[{"id":"/root","type":"ROOT","loc":{"start":0,"end":58640,"column":{"s":0,"e":3},"line":{"s":0,"e":1715}},"dim":[""],"code":null},{"id":"/root/children/0","type":"paragraph","loc":{"start":0,"end":27,"line":{"s":1,"e":1,"code":[";{ engine:dot, rankdir:LR }"]},"column":{"s":0,"e":27}},"dim":["","paragraph.0"],"code":";{ engine:dot, rankdir:LR }"},{"id":"/root/children/0/children/0","type":"text","loc":{"start":0,"end":27,"line":{"s":1,"e":1,"code":[";{ engine:dot, rankdir:LR }"]},"column":{"s":0,"e":27}},"dim":["","paragraph.0","text.0"],"code":";{ engine:dot, rankdir:LR }"},{"id":"/root/children/1","type":"heading","loc":{"start":29,"end":34,"line":{"s":3,"e":3,"code":["# mdt"]},"column":{"s":0,"e":5}},"dim":["","heading.1"],"code":"# mdt","symbName":"heading","symbRange":[36,184],"symbRangeL":[3,11],"outerCode":"\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally","outerHtml":"\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>"},{"id":"/root/children/1/children/0","type":"text","loc":{"start":31,"end":34,"line":{"s":3,"e":3,"code":["# mdt"]},"column":{"s":2,"e":5}},"dim":["","heading.1","text.0"],"code":"mdt"},{"id":"/root/children/2","type":"list","loc":{"start":36,"end":184,"line":{"s":5,"e":10,"code":["- mdd transclusion","- its runnable in nodejs","- mq-declarative-actor can run it","- sphere of fragments","- dynamic paper, space","- presented incrementally"]},"column":{"s":0,"e":25}},"dim":["","list.2"],"code":"- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally","symbName":"list","symbRange":[186,201],"symbRangeL":[5,13],"outerCode":"- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion","outerHtml":"<ul><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>"},{"id":"/root/children/2/children/0","type":"listItem","loc":{"start":36,"end":54,"line":{"s":5,"e":5,"code":["- mdd transclusion"]},"column":{"s":0,"e":18}},"dim":["","list.2","listItem.0"],"code":"- mdd transclusion"},{"id":"/root/children/2/children/0/children/0","type":"paragraph","loc":{"start":38,"end":54,"line":{"s":5,"e":5,"code":["- mdd transclusion"]},"column":{"s":2,"e":18}},"dim":["","list.2","listItem.0","paragraph.0"],"code":"mdd transclusion"},{"id":"/root/children/2/children/0/children/0/children/0","type":"text","loc":{"start":38,"end":54,"line":{"s":5,"e":5,"code":["- mdd transclusion"]},"column":{"s":2,"e":18}},"dim":["","list.2","listItem.0","paragraph.0","text.0"],"code":"mdd transclusion"},{"id":"/root/children/2/children/1","type":"listItem","loc":{"start":55,"end":79,"line":{"s":6,"e":6,"code":["- its runnable in nodejs"]},"column":{"s":0,"e":24}},"dim":["","list.2","listItem.1"],"code":"- its runnable in nodejs"},{"id":"/root/children/2/children/1/children/0","type":"paragraph","loc":{"start":57,"end":79,"line":{"s":6,"e":6,"code":["- its runnable in nodejs"]},"column":{"s":2,"e":24}},"dim":["","list.2","listItem.1","paragraph.0"],"code":"its runnable in nodejs"},{"id":"/root/children/2/children/1/children/0/children/0","type":"text","loc":{"start":57,"end":79,"line":{"s":6,"e":6,"code":["- its runnable in nodejs"]},"column":{"s":2,"e":24}},"dim":["","list.2","listItem.1","paragraph.0","text.0"],"code":"its runnable in nodejs"},{"id":"/root/children/2/children/2","type":"listItem","loc":{"start":80,"end":113,"line":{"s":7,"e":7,"code":["- mq-declarative-actor can run it"]},"column":{"s":0,"e":33}},"dim":["","list.2","listItem.2"],"code":"- mq-declarative-actor can run it"},{"id":"/root/children/2/children/2/children/0","type":"paragraph","loc":{"start":82,"end":113,"line":{"s":7,"e":7,"code":["- mq-declarative-actor can run it"]},"column":{"s":2,"e":33}},"dim":["","list.2","listItem.2","paragraph.0"],"code":"mq-declarative-actor can run it"},{"id":"/root/children/2/children/2/children/0/children/0","type":"text","loc":{"start":82,"end":113,"line":{"s":7,"e":7,"code":["- mq-declarative-actor can run it"]},"column":{"s":2,"e":33}},"dim":["","list.2","listItem.2","paragraph.0","text.0"],"code":"mq-declarative-actor can run it"},{"id":"/root/children/2/children/3","type":"listItem","loc":{"start":114,"end":135,"line":{"s":8,"e":8,"code":["- sphere of fragments"]},"column":{"s":0,"e":21}},"dim":["","list.2","listItem.3"],"code":"- sphere of fragments"},{"id":"/root/children/2/children/3/children/0","type":"paragraph","loc":{"start":116,"end":135,"line":{"s":8,"e":8,"code":["- sphere of fragments"]},"column":{"s":2,"e":21}},"dim":["","list.2","listItem.3","paragraph.0"],"code":"sphere of fragments"},{"id":"/root/children/2/children/3/children/0/children/0","type":"text","loc":{"start":116,"end":135,"line":{"s":8,"e":8,"code":["- sphere of fragments"]},"column":{"s":2,"e":21}},"dim":["","list.2","listItem.3","paragraph.0","text.0"],"code":"sphere of fragments"},{"id":"/root/children/2/children/4","type":"listItem","loc":{"start":136,"end":158,"line":{"s":9,"e":9,"code":["- dynamic paper, space"]},"column":{"s":0,"e":22}},"dim":["","list.2","listItem.4"],"code":"- dynamic paper, space"},{"id":"/root/children/2/children/4/children/0","type":"paragraph","loc":{"start":138,"end":158,"line":{"s":9,"e":9,"code":["- dynamic paper, space"]},"column":{"s":2,"e":22}},"dim":["","list.2","listItem.4","paragraph.0"],"code":"dynamic paper, space"},{"id":"/root/children/2/children/4/children/0/children/0","type":"text","loc":{"start":138,"end":158,"line":{"s":9,"e":9,"code":["- dynamic paper, space"]},"column":{"s":2,"e":22}},"dim":["","list.2","listItem.4","paragraph.0","text.0"],"code":"dynamic paper, space"},{"id":"/root/children/2/children/5","type":"listItem","loc":{"start":159,"end":184,"line":{"s":10,"e":10,"code":["- presented incrementally"]},"column":{"s":0,"e":25}},"dim":["","list.2","listItem.5"],"code":"- presented incrementally"},{"id":"/root/children/2/children/5/children/0","type":"paragraph","loc":{"start":161,"end":184,"line":{"s":10,"e":10,"code":["- presented incrementally"]},"column":{"s":2,"e":25}},"dim":["","list.2","listItem.5","paragraph.0"],"code":"presented incrementally"},{"id":"/root/children/2/children/5/children/0/children/0","type":"text","loc":{"start":161,"end":184,"line":{"s":10,"e":10,"code":["- presented incrementally"]},"column":{"s":2,"e":25}},"dim":["","list.2","listItem.5","paragraph.0","text.0"],"code":"presented incrementally"},{"id":"/root/children/3","type":"heading","loc":{"start":186,"end":201,"line":{"s":12,"e":12,"code":["## transclusion"]},"column":{"s":0,"e":15}},"dim":["","heading.3"],"code":"## transclusion","symbName":"heading","symbRange":[203,1056],"symbRangeL":[12,30],"outerCode":"\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT","outerHtml":"\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>"},{"id":"/root/children/3/children/0","type":"text","loc":{"start":189,"end":201,"line":{"s":12,"e":12,"code":["## transclusion"]},"column":{"s":3,"e":15}},"dim":["","heading.3","text.0"],"code":"transclusion"},{"id":"/root/children/4","type":"list","loc":{"start":203,"end":1056,"line":{"s":14,"e":29,"code":["- mdd transclusion is value.","- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced","- this means a tertiary virtual mdd paper can be created, which opens opportunities:","  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"","  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal","    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree","  - its similiar to [symmetric functional tree](<>)","- see meta-data","- see usage for [voting](fragment://voting)","","- valid mdd + m4","  - at instruction point (= heading)","    - insert select","    - inject select","- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)","- see TOT"]},"column":{"s":0,"e":9}},"dim":["","list.4"],"code":"- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT","symbName":"list","symbRange":[1058,1066],"symbRangeL":[14,32],"outerCode":"- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas","outerHtml":"<ul><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>"},{"id":"/root/children/4/children/0","type":"listItem","loc":{"start":203,"end":231,"line":{"s":14,"e":14,"code":["- mdd transclusion is value."]},"column":{"s":0,"e":28}},"dim":["","list.4","listItem.0"],"code":"- mdd transclusion is value."},{"id":"/root/children/4/children/0/children/0","type":"paragraph","loc":{"start":205,"end":231,"line":{"s":14,"e":14,"code":["- mdd transclusion is value."]},"column":{"s":2,"e":28}},"dim":["","list.4","listItem.0","paragraph.0"],"code":"mdd transclusion is value."},{"id":"/root/children/4/children/0/children/0/children/0","type":"text","loc":{"start":205,"end":231,"line":{"s":14,"e":14,"code":["- mdd transclusion is value."]},"column":{"s":2,"e":28}},"dim":["","list.4","listItem.0","paragraph.0","text.0"],"code":"mdd transclusion is value."},{"id":"/root/children/4/children/1","type":"listItem","loc":{"start":232,"end":328,"line":{"s":15,"e":15,"code":["- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced"]},"column":{"s":0,"e":96}},"dim":["","list.4","listItem.1"],"code":"- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced"},{"id":"/root/children/4/children/1/children/0","type":"paragraph","loc":{"start":234,"end":328,"line":{"s":15,"e":15,"code":["- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced"]},"column":{"s":2,"e":96}},"dim":["","list.4","listItem.1","paragraph.0"],"code":"using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced"},{"id":"/root/children/4/children/1/children/0/children/0","type":"text","loc":{"start":234,"end":244,"line":{"s":15,"e":15,"code":["- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced"]},"column":{"s":2,"e":12}},"dim":["","list.4","listItem.1","paragraph.0","text.0"],"code":"using the "},{"id":"/root/children/4/children/1/children/0/children/1","type":"link","loc":{"start":244,"end":289,"line":{"s":15,"e":15,"code":["- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced"]},"column":{"s":12,"e":57}},"dim":["","list.4","listItem.1","paragraph.0","link.1"],"code":"[url in heading](fragment://./url-in-heading)","symbName":"link","symbRange":[289,null],"symbRangeL":[15,14],"outerCode":"[url in heading](fragment://./url-in-heading)","outerHtml":"<p><a href=\"fragment://./url-in-heading\">url in heading</a></p>"},{"id":"/root/children/4/children/1/children/0/children/1/children/0","type":"text","loc":{"start":245,"end":259,"line":{"s":15,"e":15,"code":["- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced"]},"column":{"s":13,"e":27}},"dim":["","list.4","listItem.1","paragraph.0","link.1","text.0"],"code":"url in heading"},{"id":"/root/children/4/children/1/children/0/children/2","type":"text","loc":{"start":289,"end":328,"line":{"s":15,"e":15,"code":["- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced"]},"column":{"s":57,"e":96}},"dim":["","list.4","listItem.1","paragraph.0","text.2"],"code":" institute, fragments can be referenced"},{"id":"/root/children/4/children/2","type":"listItem","loc":{"start":329,"end":775,"line":{"s":16,"e":20,"code":["- this means a tertiary virtual mdd paper can be created, which opens opportunities:","  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"","  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal","    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree","  - its similiar to [symmetric functional tree](<>)"]},"column":{"s":0,"e":51}},"dim":["","list.4","listItem.2"],"code":"- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)"},{"id":"/root/children/4/children/2/children/0","type":"paragraph","loc":{"start":331,"end":413,"line":{"s":16,"e":16,"code":["- this means a tertiary virtual mdd paper can be created, which opens opportunities:"]},"column":{"s":2,"e":84}},"dim":["","list.4","listItem.2","paragraph.0"],"code":"this means a tertiary virtual mdd paper can be created, which opens opportunities:"},{"id":"/root/children/4/children/2/children/0/children/0","type":"text","loc":{"start":331,"end":413,"line":{"s":16,"e":16,"code":["- this means a tertiary virtual mdd paper can be created, which opens opportunities:"]},"column":{"s":2,"e":84}},"dim":["","list.4","listItem.2","paragraph.0","text.0"],"code":"this means a tertiary virtual mdd paper can be created, which opens opportunities:"},{"id":"/root/children/4/children/2/children/1","type":"list","loc":{"start":416,"end":775,"line":{"s":17,"e":20,"code":["  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"","  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal","    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree","  - its similiar to [symmetric functional tree](<>)"]},"column":{"s":2,"e":51}},"dim":["","list.4","listItem.2","list.1"],"code":"- on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)","symbName":"list","symbRange":[null,null],"symbRangeL":[17,20],"outerCode":"  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree","outerHtml":"<ul><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li></ul>"},{"id":"/root/children/4/children/2/children/1/children/0","type":"listItem","loc":{"start":416,"end":509,"line":{"s":17,"e":17,"code":["  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\""]},"column":{"s":2,"e":95}},"dim":["","list.4","listItem.2","list.1","listItem.0"],"code":"- on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\""},{"id":"/root/children/4/children/2/children/1/children/0/children/0","type":"paragraph","loc":{"start":418,"end":509,"line":{"s":17,"e":17,"code":["  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\""]},"column":{"s":4,"e":95}},"dim":["","list.4","listItem.2","list.1","listItem.0","paragraph.0"],"code":"on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\""},{"id":"/root/children/4/children/2/children/1/children/0/children/0/children/0","type":"text","loc":{"start":418,"end":509,"line":{"s":17,"e":17,"code":["  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\""]},"column":{"s":4,"e":95}},"dim":["","list.4","listItem.2","list.1","listItem.0","paragraph.0","text.0"],"code":"on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\""},{"id":"/root/children/4/children/2/children/1/children/1","type":"listItem","loc":{"start":512,"end":723,"line":{"s":18,"e":19,"code":["  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal","    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree"]},"column":{"s":2,"e":111}},"dim":["","list.4","listItem.2","list.1","listItem.1"],"code":"- on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree"},{"id":"/root/children/4/children/2/children/1/children/1/children/0","type":"paragraph","loc":{"start":514,"end":611,"line":{"s":18,"e":18,"code":["  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal"]},"column":{"s":4,"e":101}},"dim":["","list.4","listItem.2","list.1","listItem.1","paragraph.0"],"code":"on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal"},{"id":"/root/children/4/children/2/children/1/children/1/children/0/children/0","type":"text","loc":{"start":514,"end":611,"line":{"s":18,"e":18,"code":["  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal"]},"column":{"s":4,"e":101}},"dim":["","list.4","listItem.2","list.1","listItem.1","paragraph.0","text.0"],"code":"on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal"},{"id":"/root/children/4/children/2/children/1/children/1/children/1","type":"list","loc":{"start":616,"end":723,"line":{"s":19,"e":19,"code":["    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree"]},"column":{"s":4,"e":111}},"dim":["","list.4","listItem.2","list.1","listItem.1","list.1"],"code":"- the referencing anchor derives information also by its position in the structure of the mdt markdown tree","symbName":"list","symbRange":[null,null],"symbRangeL":[19,19],"outerCode":"","outerHtml":""},{"id":"/root/children/4/children/2/children/1/children/1/children/1/children/0","type":"listItem","loc":{"start":616,"end":723,"line":{"s":19,"e":19,"code":["    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree"]},"column":{"s":4,"e":111}},"dim":["","list.4","listItem.2","list.1","listItem.1","list.1","listItem.0"],"code":"- the referencing anchor derives information also by its position in the structure of the mdt markdown tree"},{"id":"/root/children/4/children/2/children/1/children/1/children/1/children/0/children/0","type":"paragraph","loc":{"start":618,"end":723,"line":{"s":19,"e":19,"code":["    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree"]},"column":{"s":6,"e":111}},"dim":["","list.4","listItem.2","list.1","listItem.1","list.1","listItem.0","paragraph.0"],"code":"the referencing anchor derives information also by its position in the structure of the mdt markdown tree"},{"id":"/root/children/4/children/2/children/1/children/1/children/1/children/0/children/0/children/0","type":"text","loc":{"start":618,"end":723,"line":{"s":19,"e":19,"code":["    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree"]},"column":{"s":6,"e":111}},"dim":["","list.4","listItem.2","list.1","listItem.1","list.1","listItem.0","paragraph.0","text.0"],"code":"the referencing anchor derives information also by its position in the structure of the mdt markdown tree"},{"id":"/root/children/4/children/2/children/1/children/2","type":"listItem","loc":{"start":726,"end":775,"line":{"s":20,"e":20,"code":["  - its similiar to [symmetric functional tree](<>)"]},"column":{"s":2,"e":51}},"dim":["","list.4","listItem.2","list.1","listItem.2"],"code":"- its similiar to [symmetric functional tree](<>)"},{"id":"/root/children/4/children/2/children/1/children/2/children/0","type":"paragraph","loc":{"start":728,"end":775,"line":{"s":20,"e":20,"code":["  - its similiar to [symmetric functional tree](<>)"]},"column":{"s":4,"e":51}},"dim":["","list.4","listItem.2","list.1","listItem.2","paragraph.0"],"code":"its similiar to [symmetric functional tree](<>)"},{"id":"/root/children/4/children/2/children/1/children/2/children/0/children/0","type":"text","loc":{"start":728,"end":744,"line":{"s":20,"e":20,"code":["  - its similiar to [symmetric functional tree](<>)"]},"column":{"s":4,"e":20}},"dim":["","list.4","listItem.2","list.1","listItem.2","paragraph.0","text.0"],"code":"its similiar to "},{"id":"/root/children/4/children/2/children/1/children/2/children/0/children/1","type":"link","loc":{"start":744,"end":775,"line":{"s":20,"e":20,"code":["  - its similiar to [symmetric functional tree](<>)"]},"column":{"s":20,"e":51}},"dim":["","list.4","listItem.2","list.1","listItem.2","paragraph.0","link.1"],"code":"[symmetric functional tree](<>)","symbName":"link","symbRange":[null,null],"symbRangeL":[20,20],"outerCode":"[symmetric functional tree](<>)","outerHtml":"<p>[symmetric functional tree](<>)</p>"},{"id":"/root/children/4/children/2/children/1/children/2/children/0/children/1/children/0","type":"text","loc":{"start":745,"end":770,"line":{"s":20,"e":20,"code":["  - its similiar to [symmetric functional tree](<>)"]},"column":{"s":21,"e":46}},"dim":["","list.4","listItem.2","list.1","listItem.2","paragraph.0","link.1","text.0"],"code":"symmetric functional tree"},{"id":"/root/children/4/children/3","type":"listItem","loc":{"start":776,"end":791,"line":{"s":21,"e":21,"code":["- see meta-data"]},"column":{"s":0,"e":15}},"dim":["","list.4","listItem.3"],"code":"- see meta-data"},{"id":"/root/children/4/children/3/children/0","type":"paragraph","loc":{"start":778,"end":791,"line":{"s":21,"e":21,"code":["- see meta-data"]},"column":{"s":2,"e":15}},"dim":["","list.4","listItem.3","paragraph.0"],"code":"see meta-data"},{"id":"/root/children/4/children/3/children/0/children/0","type":"text","loc":{"start":778,"end":791,"line":{"s":21,"e":21,"code":["- see meta-data"]},"column":{"s":2,"e":15}},"dim":["","list.4","listItem.3","paragraph.0","text.0"],"code":"see meta-data"},{"id":"/root/children/4/children/4","type":"listItem","loc":{"start":792,"end":835,"line":{"s":22,"e":22,"code":["- see usage for [voting](fragment://voting)"]},"column":{"s":0,"e":43}},"dim":["","list.4","listItem.4"],"code":"- see usage for [voting](fragment://voting)"},{"id":"/root/children/4/children/4/children/0","type":"paragraph","loc":{"start":794,"end":835,"line":{"s":22,"e":22,"code":["- see usage for [voting](fragment://voting)"]},"column":{"s":2,"e":43}},"dim":["","list.4","listItem.4","paragraph.0"],"code":"see usage for [voting](fragment://voting)"},{"id":"/root/children/4/children/4/children/0/children/0","type":"text","loc":{"start":794,"end":808,"line":{"s":22,"e":22,"code":["- see usage for [voting](fragment://voting)"]},"column":{"s":2,"e":16}},"dim":["","list.4","listItem.4","paragraph.0","text.0"],"code":"see usage for "},{"id":"/root/children/4/children/4/children/0/children/1","type":"link","loc":{"start":808,"end":835,"line":{"s":22,"e":22,"code":["- see usage for [voting](fragment://voting)"]},"column":{"s":16,"e":43}},"dim":["","list.4","listItem.4","paragraph.0","link.1"],"code":"[voting](fragment://voting)","symbName":"link","symbRange":[null,null],"symbRangeL":[22,22],"outerCode":"[voting](fragment://voting)","outerHtml":"<p><a href=\"fragment://voting\">voting</a></p>"},{"id":"/root/children/4/children/4/children/0/children/1/children/0","type":"text","loc":{"start":809,"end":815,"line":{"s":22,"e":22,"code":["- see usage for [voting](fragment://voting)"]},"column":{"s":17,"e":23}},"dim":["","list.4","listItem.4","paragraph.0","link.1","text.0"],"code":"voting"},{"id":"/root/children/4/children/5","type":"listItem","loc":{"start":837,"end":930,"line":{"s":24,"e":27,"code":["- valid mdd + m4","  - at instruction point (= heading)","    - insert select","    - inject select"]},"column":{"s":0,"e":19}},"dim":["","list.4","listItem.5"],"code":"- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select"},{"id":"/root/children/4/children/5/children/0","type":"paragraph","loc":{"start":839,"end":853,"line":{"s":24,"e":24,"code":["- valid mdd + m4"]},"column":{"s":2,"e":16}},"dim":["","list.4","listItem.5","paragraph.0"],"code":"valid mdd + m4"},{"id":"/root/children/4/children/5/children/0/children/0","type":"text","loc":{"start":839,"end":853,"line":{"s":24,"e":24,"code":["- valid mdd + m4"]},"column":{"s":2,"e":16}},"dim":["","list.4","listItem.5","paragraph.0","text.0"],"code":"valid mdd + m4"},{"id":"/root/children/4/children/5/children/1","type":"list","loc":{"start":856,"end":930,"line":{"s":25,"e":27,"code":["  - at instruction point (= heading)","    - insert select","    - inject select"]},"column":{"s":2,"e":19}},"dim":["","list.4","listItem.5","list.1"],"code":"- at instruction point (= heading)\n    - insert select\n    - inject select","symbName":"list","symbRange":[null,null],"symbRangeL":[25,27],"outerCode":"    - insert select","outerHtml":"<pre><code>- insert select</code></pre>"},{"id":"/root/children/4/children/5/children/1/children/0","type":"listItem","loc":{"start":856,"end":930,"line":{"s":25,"e":27,"code":["  - at instruction point (= heading)","    - insert select","    - inject select"]},"column":{"s":2,"e":19}},"dim":["","list.4","listItem.5","list.1","listItem.0"],"code":"- at instruction point (= heading)\n    - insert select\n    - inject select"},{"id":"/root/children/4/children/5/children/1/children/0/children/0","type":"paragraph","loc":{"start":858,"end":890,"line":{"s":25,"e":25,"code":["  - at instruction point (= heading)"]},"column":{"s":4,"e":36}},"dim":["","list.4","listItem.5","list.1","listItem.0","paragraph.0"],"code":"at instruction point (= heading)"},{"id":"/root/children/4/children/5/children/1/children/0/children/0/children/0","type":"text","loc":{"start":858,"end":890,"line":{"s":25,"e":25,"code":["  - at instruction point (= heading)"]},"column":{"s":4,"e":36}},"dim":["","list.4","listItem.5","list.1","listItem.0","paragraph.0","text.0"],"code":"at instruction point (= heading)"},{"id":"/root/children/4/children/5/children/1/children/0/children/1","type":"list","loc":{"start":895,"end":930,"line":{"s":26,"e":27,"code":["    - insert select","    - inject select"]},"column":{"s":4,"e":19}},"dim":["","list.4","listItem.5","list.1","listItem.0","list.1"],"code":"- insert select\n    - inject select","symbName":"list","symbRange":[null,null],"symbRangeL":[26,27],"outerCode":"","outerHtml":""},{"id":"/root/children/4/children/5/children/1/children/0/children/1/children/0","type":"listItem","loc":{"start":895,"end":910,"line":{"s":26,"e":26,"code":["    - insert select"]},"column":{"s":4,"e":19}},"dim":["","list.4","listItem.5","list.1","listItem.0","list.1","listItem.0"],"code":"- insert select"},{"id":"/root/children/4/children/5/children/1/children/0/children/1/children/0/children/0","type":"paragraph","loc":{"start":897,"end":910,"line":{"s":26,"e":26,"code":["    - insert select"]},"column":{"s":6,"e":19}},"dim":["","list.4","listItem.5","list.1","listItem.0","list.1","listItem.0","paragraph.0"],"code":"insert select"},{"id":"/root/children/4/children/5/children/1/children/0/children/1/children/0/children/0/children/0","type":"text","loc":{"start":897,"end":910,"line":{"s":26,"e":26,"code":["    - insert select"]},"column":{"s":6,"e":19}},"dim":["","list.4","listItem.5","list.1","listItem.0","list.1","listItem.0","paragraph.0","text.0"],"code":"insert select"},{"id":"/root/children/4/children/5/children/1/children/0/children/1/children/1","type":"listItem","loc":{"start":915,"end":930,"line":{"s":27,"e":27,"code":["    - inject select"]},"column":{"s":4,"e":19}},"dim":["","list.4","listItem.5","list.1","listItem.0","list.1","listItem.1"],"code":"- inject select"},{"id":"/root/children/4/children/5/children/1/children/0/children/1/children/1/children/0","type":"paragraph","loc":{"start":917,"end":930,"line":{"s":27,"e":27,"code":["    - inject select"]},"column":{"s":6,"e":19}},"dim":["","list.4","listItem.5","list.1","listItem.0","list.1","listItem.1","paragraph.0"],"code":"inject select"},{"id":"/root/children/4/children/5/children/1/children/0/children/1/children/1/children/0/children/0","type":"text","loc":{"start":917,"end":930,"line":{"s":27,"e":27,"code":["    - inject select"]},"column":{"s":6,"e":19}},"dim":["","list.4","listItem.5","list.1","listItem.0","list.1","listItem.1","paragraph.0","text.0"],"code":"inject select"},{"id":"/root/children/4/children/6","type":"listItem","loc":{"start":931,"end":1046,"line":{"s":28,"e":28,"code":["- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)"]},"column":{"s":0,"e":115}},"dim":["","list.4","listItem.6"],"code":"- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)"},{"id":"/root/children/4/children/6/children/0","type":"paragraph","loc":{"start":933,"end":1046,"line":{"s":28,"e":28,"code":["- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)"]},"column":{"s":2,"e":115}},"dim":["","list.4","listItem.6","paragraph.0"],"code":"[mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)"},{"id":"/root/children/4/children/6/children/0/children/0","type":"link","loc":{"start":933,"end":1046,"line":{"s":28,"e":28,"code":["- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)"]},"column":{"s":2,"e":115}},"dim":["","list.4","listItem.6","paragraph.0","link.0"],"code":"[mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)","symbName":"link","symbRange":[null,null],"symbRangeL":[28,28],"outerCode":"[mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)","outerHtml":"<p><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></p>"},{"id":"/root/children/4/children/6/children/0/children/0/children/0","type":"text","loc":{"start":934,"end":973,"line":{"s":28,"e":28,"code":["- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)"]},"column":{"s":3,"e":42}},"dim":["","list.4","listItem.6","paragraph.0","link.0","text.0"],"code":"mdt — Markdown Construction Pseudo-Code"},{"id":"/root/children/4/children/7","type":"listItem","loc":{"start":1047,"end":1056,"line":{"s":29,"e":29,"code":["- see TOT"]},"column":{"s":0,"e":9}},"dim":["","list.4","listItem.7"],"code":"- see TOT"},{"id":"/root/children/4/children/7/children/0","type":"paragraph","loc":{"start":1049,"end":1056,"line":{"s":29,"e":29,"code":["- see TOT"]},"column":{"s":2,"e":9}},"dim":["","list.4","listItem.7","paragraph.0"],"code":"see TOT"},{"id":"/root/children/4/children/7/children/0/children/0","type":"text","loc":{"start":1049,"end":1056,"line":{"s":29,"e":29,"code":["- see TOT"]},"column":{"s":2,"e":9}},"dim":["","list.4","listItem.7","paragraph.0","text.0"],"code":"see TOT"},{"id":"/root/children/5","type":"heading","loc":{"start":1058,"end":1066,"line":{"s":31,"e":31,"code":["## ideas"]},"column":{"s":0,"e":8}},"dim":["","heading.5"],"code":"## ideas","symbName":"heading","symbRange":[1068,1239],"symbRangeL":[31,38],"outerCode":"\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,","outerHtml":"\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>"},{"id":"/root/children/5/children/0","type":"text","loc":{"start":1061,"end":1066,"line":{"s":31,"e":31,"code":["## ideas"]},"column":{"s":3,"e":8}},"dim":["","heading.5","text.0"],"code":"ideas"},{"id":"/root/children/6","type":"list","loc":{"start":1068,"end":1239,"line":{"s":33,"e":37,"code":["- an extruction can have the codeblock and also text","- insert is fetching cached content of fragments","- backend?","  - final mdd will be produced?","  - makes sense for space,"]},"column":{"s":0,"e":26}},"dim":["","list.6"],"code":"- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,","symbName":"list","symbRange":[1241,1582],"symbRangeL":[33,47],"outerCode":"- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:","outerHtml":"<ul><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>"},{"id":"/root/children/6/children/0","type":"listItem","loc":{"start":1068,"end":1120,"line":{"s":33,"e":33,"code":["- an extruction can have the codeblock and also text"]},"column":{"s":0,"e":52}},"dim":["","list.6","listItem.0"],"code":"- an extruction can have the codeblock and also text"},{"id":"/root/children/6/children/0/children/0","type":"paragraph","loc":{"start":1070,"end":1120,"line":{"s":33,"e":33,"code":["- an extruction can have the codeblock and also text"]},"column":{"s":2,"e":52}},"dim":["","list.6","listItem.0","paragraph.0"],"code":"an extruction can have the codeblock and also text"},{"id":"/root/children/6/children/0/children/0/children/0","type":"text","loc":{"start":1070,"end":1120,"line":{"s":33,"e":33,"code":["- an extruction can have the codeblock and also text"]},"column":{"s":2,"e":52}},"dim":["","list.6","listItem.0","paragraph.0","text.0"],"code":"an extruction can have the codeblock and also text"},{"id":"/root/children/6/children/1","type":"listItem","loc":{"start":1121,"end":1169,"line":{"s":34,"e":34,"code":["- insert is fetching cached content of fragments"]},"column":{"s":0,"e":48}},"dim":["","list.6","listItem.1"],"code":"- insert is fetching cached content of fragments"},{"id":"/root/children/6/children/1/children/0","type":"paragraph","loc":{"start":1123,"end":1169,"line":{"s":34,"e":34,"code":["- insert is fetching cached content of fragments"]},"column":{"s":2,"e":48}},"dim":["","list.6","listItem.1","paragraph.0"],"code":"insert is fetching cached content of fragments"},{"id":"/root/children/6/children/1/children/0/children/0","type":"text","loc":{"start":1123,"end":1169,"line":{"s":34,"e":34,"code":["- insert is fetching cached content of fragments"]},"column":{"s":2,"e":48}},"dim":["","list.6","listItem.1","paragraph.0","text.0"],"code":"insert is fetching cached content of fragments"},{"id":"/root/children/6/children/2","type":"listItem","loc":{"start":1170,"end":1239,"line":{"s":35,"e":37,"code":["- backend?","  - final mdd will be produced?","  - makes sense for space,"]},"column":{"s":0,"e":26}},"dim":["","list.6","listItem.2"],"code":"- backend?\n  - final mdd will be produced?\n  - makes sense for space,"},{"id":"/root/children/6/children/2/children/0","type":"paragraph","loc":{"start":1172,"end":1180,"line":{"s":35,"e":35,"code":["- backend?"]},"column":{"s":2,"e":10}},"dim":["","list.6","listItem.2","paragraph.0"],"code":"backend?"},{"id":"/root/children/6/children/2/children/0/children/0","type":"text","loc":{"start":1172,"end":1180,"line":{"s":35,"e":35,"code":["- backend?"]},"column":{"s":2,"e":10}},"dim":["","list.6","listItem.2","paragraph.0","text.0"],"code":"backend?"},{"id":"/root/children/6/children/2/children/1","type":"list","loc":{"start":1183,"end":1239,"line":{"s":36,"e":37,"code":["  - final mdd will be produced?","  - makes sense for space,"]},"column":{"s":2,"e":26}},"dim":["","list.6","listItem.2","list.1"],"code":"- final mdd will be produced?\n  - makes sense for space,","symbName":"list","symbRange":[null,null],"symbRangeL":[36,37],"outerCode":"","outerHtml":""},{"id":"/root/children/6/children/2/children/1/children/0","type":"listItem","loc":{"start":1183,"end":1212,"line":{"s":36,"e":36,"code":["  - final mdd will be produced?"]},"column":{"s":2,"e":31}},"dim":["","list.6","listItem.2","list.1","listItem.0"],"code":"- final mdd will be produced?"},{"id":"/root/children/6/children/2/children/1/children/0/children/0","type":"paragraph","loc":{"start":1185,"end":1212,"line":{"s":36,"e":36,"code":["  - final mdd will be produced?"]},"column":{"s":4,"e":31}},"dim":["","list.6","listItem.2","list.1","listItem.0","paragraph.0"],"code":"final mdd will be produced?"},{"id":"/root/children/6/children/2/children/1/children/0/children/0/children/0","type":"text","loc":{"start":1185,"end":1212,"line":{"s":36,"e":36,"code":["  - final mdd will be produced?"]},"column":{"s":4,"e":31}},"dim":["","list.6","listItem.2","list.1","listItem.0","paragraph.0","text.0"],"code":"final mdd will be produced?"},{"id":"/root/children/6/children/2/children/1/children/1","type":"listItem","loc":{"start":1215,"end":1239,"line":{"s":37,"e":37,"code":["  - makes sense for space,"]},"column":{"s":2,"e":26}},"dim":["","list.6","listItem.2","list.1","listItem.1"],"code":"- makes sense for space,"},{"id":"/root/children/6/children/2/children/1/children/1/children/0","type":"paragraph","loc":{"start":1217,"end":1239,"line":{"s":37,"e":37,"code":["  - makes sense for space,"]},"column":{"s":4,"e":26}},"dim":["","list.6","listItem.2","list.1","listItem.1","paragraph.0"],"code":"makes sense for space,"},{"id":"/root/children/6/children/2/children/1/children/1/children/0/children/0","type":"text","loc":{"start":1217,"end":1239,"line":{"s":37,"e":37,"code":["  - makes sense for space,"]},"column":{"s":4,"e":26}},"dim":["","list.6","listItem.2","list.1","listItem.1","paragraph.0","text.0"],"code":"makes sense for space,"},{"id":"/root/children/7","type":"heading","loc":{"start":1241,"end":1287,"line":{"s":39,"e":39,"code":["# mdt — Markdown Construction Pseudo-Code Spec"]},"column":{"s":0,"e":46}},"dim":["","heading.7"],"code":"# mdt — Markdown Construction Pseudo-Code Spec","symbName":"heading","symbRange":[1289,1921],"symbRangeL":[39,54],"outerCode":"\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.","outerHtml":"\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>"},{"id":"/root/children/7/children/0","type":"text","loc":{"start":1243,"end":1287,"line":{"s":39,"e":39,"code":["# mdt — Markdown Construction Pseudo-Code Spec"]},"column":{"s":2,"e":46}},"dim":["","heading.7","text.0"],"code":"mdt — Markdown Construction Pseudo-Code Spec"},{"id":"/root/children/8","type":"paragraph","loc":{"start":1289,"end":1531,"line":{"s":41,"e":44,"code":["Pure JavaScript library for a **markdown construction pseudo-code language**.","Markdown is the surface syntax.","`# ${...}` headings are **extructions** — labeled markers that","produce no output; bodies use ` ```javascript ` code blocks for eval."]},"column":{"s":0,"e":69}},"dim":["","paragraph.8"],"code":"Pure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval."},{"id":"/root/children/8/children/0","type":"text","loc":{"start":1289,"end":1319,"line":{"s":41,"e":41,"code":["Pure JavaScript library for a **markdown construction pseudo-code language**."]},"column":{"s":0,"e":30}},"dim":["","paragraph.8","text.0"],"code":"Pure JavaScript library for a "},{"id":"/root/children/8/children/1","type":"strong","loc":{"start":1319,"end":1365,"line":{"s":41,"e":41,"code":["Pure JavaScript library for a **markdown construction pseudo-code language**."]},"column":{"s":30,"e":76}},"dim":["","paragraph.8","strong.1"],"code":"**markdown construction pseudo-code language**"},{"id":"/root/children/8/children/1/children/0","type":"text","loc":{"start":1321,"end":1363,"line":{"s":41,"e":41,"code":["Pure JavaScript library for a **markdown construction pseudo-code language**."]},"column":{"s":32,"e":74}},"dim":["","paragraph.8","strong.1","text.0"],"code":"markdown construction pseudo-code language"},{"id":"/root/children/8/children/2","type":"text","loc":{"start":1365,"end":1399,"line":{"s":41,"e":43,"code":["Pure JavaScript library for a **markdown construction pseudo-code language**.","Markdown is the surface syntax.","`# ${...}` headings are **extructions** — labeled markers that"]},"column":{"s":76,"e":0}},"dim":["","paragraph.8","text.2"],"code":".\nMarkdown is the surface syntax.\n"},{"id":"/root/children/8/children/3","type":"inlineCode","loc":{"start":1399,"end":1409,"line":{"s":43,"e":43,"code":["`# ${...}` headings are **extructions** — labeled markers that"]},"column":{"s":0,"e":10}},"dim":["","paragraph.8","inlineCode.3"],"code":"`# ${...}`"},{"id":"/root/children/8/children/4","type":"text","loc":{"start":1409,"end":1423,"line":{"s":43,"e":43,"code":["`# ${...}` headings are **extructions** — labeled markers that"]},"column":{"s":10,"e":24}},"dim":["","paragraph.8","text.4"],"code":" headings are "},{"id":"/root/children/8/children/5","type":"strong","loc":{"start":1423,"end":1438,"line":{"s":43,"e":43,"code":["`# ${...}` headings are **extructions** — labeled markers that"]},"column":{"s":24,"e":39}},"dim":["","paragraph.8","strong.5"],"code":"**extructions**"},{"id":"/root/children/8/children/5/children/0","type":"text","loc":{"start":1425,"end":1436,"line":{"s":43,"e":43,"code":["`# ${...}` headings are **extructions** — labeled markers that"]},"column":{"s":26,"e":37}},"dim":["","paragraph.8","strong.5","text.0"],"code":"extructions"},{"id":"/root/children/8/children/6","type":"text","loc":{"start":1438,"end":1492,"line":{"s":43,"e":44,"code":["`# ${...}` headings are **extructions** — labeled markers that","produce no output; bodies use ` ```javascript ` code blocks for eval."]},"column":{"s":39,"e":30}},"dim":["","paragraph.8","text.6"],"code":" — labeled markers that\nproduce no output; bodies use "},{"id":"/root/children/8/children/7","type":"inlineCode","loc":{"start":1492,"end":1509,"line":{"s":44,"e":44,"code":["produce no output; bodies use ` ```javascript ` code blocks for eval."]},"column":{"s":30,"e":47}},"dim":["","paragraph.8","inlineCode.7"],"code":"` ```javascript `"},{"id":"/root/children/8/children/8","type":"text","loc":{"start":1509,"end":1531,"line":{"s":44,"e":44,"code":["produce no output; bodies use ` ```javascript ` code blocks for eval."]},"column":{"s":47,"e":69}},"dim":["","paragraph.8","text.8"],"code":" code blocks for eval."},{"id":"/root/children/9","type":"paragraph","loc":{"start":1533,"end":1582,"line":{"s":46,"e":46,"code":["The library follows a **compile / runner** split:"]},"column":{"s":0,"e":49}},"dim":["","paragraph.9"],"code":"The library follows a **compile / runner** split:"},{"id":"/root/children/9/children/0","type":"text","loc":{"start":1533,"end":1555,"line":{"s":46,"e":46,"code":["The library follows a **compile / runner** split:"]},"column":{"s":0,"e":22}},"dim":["","paragraph.9","text.0"],"code":"The library follows a "},{"id":"/root/children/9/children/1","type":"strong","loc":{"start":1555,"end":1575,"line":{"s":46,"e":46,"code":["The library follows a **compile / runner** split:"]},"column":{"s":22,"e":42}},"dim":["","paragraph.9","strong.1"],"code":"**compile / runner**"},{"id":"/root/children/9/children/1/children/0","type":"text","loc":{"start":1557,"end":1573,"line":{"s":46,"e":46,"code":["The library follows a **compile / runner** split:"]},"column":{"s":24,"e":40}},"dim":["","paragraph.9","strong.1","text.0"],"code":"compile / runner"},{"id":"/root/children/9/children/2","type":"text","loc":{"start":1575,"end":1582,"line":{"s":46,"e":46,"code":["The library follows a **compile / runner** split:"]},"column":{"s":42,"e":49}},"dim":["","paragraph.9","text.2"],"code":" split:"},{"id":"/root/children/10","type":"list","loc":{"start":1584,"end":1792,"line":{"s":48,"e":50,"code":["- `compile(mdtText, { remark })` — static analysis, returns a `Runner`","- The `Runner` is a function — call it with context and opts to","  get a **Document**, which lazily yields expandable **Fragment** objects"]},"column":{"s":0,"e":73}},"dim":["","list.10"],"code":"- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects","symbName":"list","symbRange":[1794,1934],"symbRangeL":[48,56],"outerCode":"- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea","outerHtml":"<ul><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>"},{"id":"/root/children/10/children/0","type":"listItem","loc":{"start":1584,"end":1654,"line":{"s":48,"e":48,"code":["- `compile(mdtText, { remark })` — static analysis, returns a `Runner`"]},"column":{"s":0,"e":70}},"dim":["","list.10","listItem.0"],"code":"- `compile(mdtText, { remark })` — static analysis, returns a `Runner`"},{"id":"/root/children/10/children/0/children/0","type":"paragraph","loc":{"start":1586,"end":1654,"line":{"s":48,"e":48,"code":["- `compile(mdtText, { remark })` — static analysis, returns a `Runner`"]},"column":{"s":2,"e":70}},"dim":["","list.10","listItem.0","paragraph.0"],"code":"`compile(mdtText, { remark })` — static analysis, returns a `Runner`"},{"id":"/root/children/10/children/0/children/0/children/0","type":"inlineCode","loc":{"start":1586,"end":1616,"line":{"s":48,"e":48,"code":["- `compile(mdtText, { remark })` — static analysis, returns a `Runner`"]},"column":{"s":2,"e":32}},"dim":["","list.10","listItem.0","paragraph.0","inlineCode.0"],"code":"`compile(mdtText, { remark })`"},{"id":"/root/children/10/children/0/children/0/children/1","type":"text","loc":{"start":1616,"end":1646,"line":{"s":48,"e":48,"code":["- `compile(mdtText, { remark })` — static analysis, returns a `Runner`"]},"column":{"s":32,"e":62}},"dim":["","list.10","listItem.0","paragraph.0","text.1"],"code":" — static analysis, returns a "},{"id":"/root/children/10/children/0/children/0/children/2","type":"inlineCode","loc":{"start":1646,"end":1654,"line":{"s":48,"e":48,"code":["- `compile(mdtText, { remark })` — static analysis, returns a `Runner`"]},"column":{"s":62,"e":70}},"dim":["","list.10","listItem.0","paragraph.0","inlineCode.2"],"code":"`Runner`"},{"id":"/root/children/10/children/1","type":"listItem","loc":{"start":1655,"end":1792,"line":{"s":49,"e":50,"code":["- The `Runner` is a function — call it with context and opts to","  get a **Document**, which lazily yields expandable **Fragment** objects"]},"column":{"s":0,"e":73}},"dim":["","list.10","listItem.1"],"code":"- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects"},{"id":"/root/children/10/children/1/children/0","type":"paragraph","loc":{"start":1657,"end":1792,"line":{"s":49,"e":50,"code":["- The `Runner` is a function — call it with context and opts to","  get a **Document**, which lazily yields expandable **Fragment** objects"]},"column":{"s":2,"e":73}},"dim":["","list.10","listItem.1","paragraph.0"],"code":"The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects"},{"id":"/root/children/10/children/1/children/0/children/0","type":"text","loc":{"start":1657,"end":1661,"line":{"s":49,"e":49,"code":["- The `Runner` is a function — call it with context and opts to"]},"column":{"s":2,"e":6}},"dim":["","list.10","listItem.1","paragraph.0","text.0"],"code":"The "},{"id":"/root/children/10/children/1/children/0/children/1","type":"inlineCode","loc":{"start":1661,"end":1669,"line":{"s":49,"e":49,"code":["- The `Runner` is a function — call it with context and opts to"]},"column":{"s":6,"e":14}},"dim":["","list.10","listItem.1","paragraph.0","inlineCode.1"],"code":"`Runner`"},{"id":"/root/children/10/children/1/children/0/children/2","type":"text","loc":{"start":1669,"end":1727,"line":{"s":49,"e":50,"code":["- The `Runner` is a function — call it with context and opts to","  get a **Document**, which lazily yields expandable **Fragment** objects"]},"column":{"s":14,"e":8}},"dim":["","list.10","listItem.1","paragraph.0","text.2"],"code":" is a function — call it with context and opts to\n  get a "},{"id":"/root/children/10/children/1/children/0/children/3","type":"strong","loc":{"start":1727,"end":1739,"line":{"s":50,"e":50,"code":["  get a **Document**, which lazily yields expandable **Fragment** objects"]},"column":{"s":8,"e":20}},"dim":["","list.10","listItem.1","paragraph.0","strong.3"],"code":"**Document**"},{"id":"/root/children/10/children/1/children/0/children/3/children/0","type":"text","loc":{"start":1729,"end":1737,"line":{"s":50,"e":50,"code":["  get a **Document**, which lazily yields expandable **Fragment** objects"]},"column":{"s":10,"e":18}},"dim":["","list.10","listItem.1","paragraph.0","strong.3","text.0"],"code":"Document"},{"id":"/root/children/10/children/1/children/0/children/4","type":"text","loc":{"start":1739,"end":1772,"line":{"s":50,"e":50,"code":["  get a **Document**, which lazily yields expandable **Fragment** objects"]},"column":{"s":20,"e":53}},"dim":["","list.10","listItem.1","paragraph.0","text.4"],"code":", which lazily yields expandable "},{"id":"/root/children/10/children/1/children/0/children/5","type":"strong","loc":{"start":1772,"end":1784,"line":{"s":50,"e":50,"code":["  get a **Document**, which lazily yields expandable **Fragment** objects"]},"column":{"s":53,"e":65}},"dim":["","list.10","listItem.1","paragraph.0","strong.5"],"code":"**Fragment**"},{"id":"/root/children/10/children/1/children/0/children/5/children/0","type":"text","loc":{"start":1774,"end":1782,"line":{"s":50,"e":50,"code":["  get a **Document**, which lazily yields expandable **Fragment** objects"]},"column":{"s":55,"e":63}},"dim":["","list.10","listItem.1","paragraph.0","strong.5","text.0"],"code":"Fragment"},{"id":"/root/children/10/children/1/children/0/children/6","type":"text","loc":{"start":1784,"end":1792,"line":{"s":50,"e":50,"code":["  get a **Document**, which lazily yields expandable **Fragment** objects"]},"column":{"s":65,"e":73}},"dim":["","list.10","listItem.1","paragraph.0","text.6"],"code":" objects"},{"id":"/root/children/11","type":"paragraph","loc":{"start":1794,"end":1921,"line":{"s":52,"e":53,"code":["All functions are **pure** — no mutation of inputs, no side effects,","no classes, all external dependencies passed as arguments."]},"column":{"s":0,"e":58}},"dim":["","paragraph.11"],"code":"All functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments."},{"id":"/root/children/11/children/0","type":"text","loc":{"start":1794,"end":1812,"line":{"s":52,"e":52,"code":["All functions are **pure** — no mutation of inputs, no side effects,"]},"column":{"s":0,"e":18}},"dim":["","paragraph.11","text.0"],"code":"All functions are "},{"id":"/root/children/11/children/1","type":"strong","loc":{"start":1812,"end":1820,"line":{"s":52,"e":52,"code":["All functions are **pure** — no mutation of inputs, no side effects,"]},"column":{"s":18,"e":26}},"dim":["","paragraph.11","strong.1"],"code":"**pure**"},{"id":"/root/children/11/children/1/children/0","type":"text","loc":{"start":1814,"end":1818,"line":{"s":52,"e":52,"code":["All functions are **pure** — no mutation of inputs, no side effects,"]},"column":{"s":20,"e":24}},"dim":["","paragraph.11","strong.1","text.0"],"code":"pure"},{"id":"/root/children/11/children/2","type":"text","loc":{"start":1820,"end":1921,"line":{"s":52,"e":53,"code":["All functions are **pure** — no mutation of inputs, no side effects,","no classes, all external dependencies passed as arguments."]},"column":{"s":26,"e":58}},"dim":["","paragraph.11","text.2"],"code":" — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments."},{"id":"/root/children/12","type":"heading","loc":{"start":1923,"end":1934,"line":{"s":55,"e":55,"code":["## The idea"]},"column":{"s":0,"e":11}},"dim":["","heading.12"],"code":"## The idea","symbName":"heading","symbRange":[1936,2708],"symbRangeL":[55,75],"outerCode":"\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.","outerHtml":"\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>"},{"id":"/root/children/12/children/0","type":"text","loc":{"start":1926,"end":1934,"line":{"s":55,"e":55,"code":["## The idea"]},"column":{"s":3,"e":11}},"dim":["","heading.12","text.0"],"code":"The idea"},{"id":"/root/children/13","type":"list","loc":{"start":1936,"end":1981,"line":{"s":57,"e":58,"code":["- sphere of fragments","- dynamic markdown OLAP"]},"column":{"s":0,"e":23}},"dim":["","list.13"],"code":"- sphere of fragments\n- dynamic markdown OLAP","symbName":"list","symbRange":[1983,2225],"symbRangeL":[57,65],"outerCode":"- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:","outerHtml":"<ul><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>"},{"id":"/root/children/13/children/0","type":"listItem","loc":{"start":1936,"end":1957,"line":{"s":57,"e":57,"code":["- sphere of fragments"]},"column":{"s":0,"e":21}},"dim":["","list.13","listItem.0"],"code":"- sphere of fragments"},{"id":"/root/children/13/children/0/children/0","type":"paragraph","loc":{"start":1938,"end":1957,"line":{"s":57,"e":57,"code":["- sphere of fragments"]},"column":{"s":2,"e":21}},"dim":["","list.13","listItem.0","paragraph.0"],"code":"sphere of fragments"},{"id":"/root/children/13/children/0/children/0/children/0","type":"text","loc":{"start":1938,"end":1957,"line":{"s":57,"e":57,"code":["- sphere of fragments"]},"column":{"s":2,"e":21}},"dim":["","list.13","listItem.0","paragraph.0","text.0"],"code":"sphere of fragments"},{"id":"/root/children/13/children/1","type":"listItem","loc":{"start":1958,"end":1981,"line":{"s":58,"e":58,"code":["- dynamic markdown OLAP"]},"column":{"s":0,"e":23}},"dim":["","list.13","listItem.1"],"code":"- dynamic markdown OLAP"},{"id":"/root/children/13/children/1/children/0","type":"paragraph","loc":{"start":1960,"end":1981,"line":{"s":58,"e":58,"code":["- dynamic markdown OLAP"]},"column":{"s":2,"e":23}},"dim":["","list.13","listItem.1","paragraph.0"],"code":"dynamic markdown OLAP"},{"id":"/root/children/13/children/1/children/0/children/0","type":"text","loc":{"start":1960,"end":1981,"line":{"s":58,"e":58,"code":["- dynamic markdown OLAP"]},"column":{"s":2,"e":23}},"dim":["","list.13","listItem.1","paragraph.0","text.0"],"code":"dynamic markdown OLAP"},{"id":"/root/children/14","type":"paragraph","loc":{"start":1983,"end":2165,"line":{"s":60,"e":62,"code":["The `# ${...}` construct is called an **extruction** — a coined term for","a labeled heading marker that produces no output;","the body uses ` ```javascript ` code blocks for evaluation."]},"column":{"s":0,"e":59}},"dim":["","paragraph.14"],"code":"The `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation."},{"id":"/root/children/14/children/0","type":"text","loc":{"start":1983,"end":1987,"line":{"s":60,"e":60,"code":["The `# ${...}` construct is called an **extruction** — a coined term for"]},"column":{"s":0,"e":4}},"dim":["","paragraph.14","text.0"],"code":"The "},{"id":"/root/children/14/children/1","type":"inlineCode","loc":{"start":1987,"end":1997,"line":{"s":60,"e":60,"code":["The `# ${...}` construct is called an **extruction** — a coined term for"]},"column":{"s":4,"e":14}},"dim":["","paragraph.14","inlineCode.1"],"code":"`# ${...}`"},{"id":"/root/children/14/children/2","type":"text","loc":{"start":1997,"end":2021,"line":{"s":60,"e":60,"code":["The `# ${...}` construct is called an **extruction** — a coined term for"]},"column":{"s":14,"e":38}},"dim":["","paragraph.14","text.2"],"code":" construct is called an "},{"id":"/root/children/14/children/3","type":"strong","loc":{"start":2021,"end":2035,"line":{"s":60,"e":60,"code":["The `# ${...}` construct is called an **extruction** — a coined term for"]},"column":{"s":38,"e":52}},"dim":["","paragraph.14","strong.3"],"code":"**extruction**"},{"id":"/root/children/14/children/3/children/0","type":"text","loc":{"start":2023,"end":2033,"line":{"s":60,"e":60,"code":["The `# ${...}` construct is called an **extruction** — a coined term for"]},"column":{"s":40,"e":50}},"dim":["","paragraph.14","strong.3","text.0"],"code":"extruction"},{"id":"/root/children/14/children/4","type":"text","loc":{"start":2035,"end":2120,"line":{"s":60,"e":62,"code":["The `# ${...}` construct is called an **extruction** — a coined term for","a labeled heading marker that produces no output;","the body uses ` ```javascript ` code blocks for evaluation."]},"column":{"s":52,"e":14}},"dim":["","paragraph.14","text.4"],"code":" — a coined term for\na labeled heading marker that produces no output;\nthe body uses "},{"id":"/root/children/14/children/5","type":"inlineCode","loc":{"start":2120,"end":2137,"line":{"s":62,"e":62,"code":["the body uses ` ```javascript ` code blocks for evaluation."]},"column":{"s":14,"e":31}},"dim":["","paragraph.14","inlineCode.5"],"code":"` ```javascript `"},{"id":"/root/children/14/children/6","type":"text","loc":{"start":2137,"end":2165,"line":{"s":62,"e":62,"code":["the body uses ` ```javascript ` code blocks for evaluation."]},"column":{"s":31,"e":59}},"dim":["","paragraph.14","text.6"],"code":" code blocks for evaluation."},{"id":"/root/children/15","type":"paragraph","loc":{"start":2167,"end":2225,"line":{"s":64,"e":64,"code":["The name evolved through several candidates during design:"]},"column":{"s":0,"e":58}},"dim":["","paragraph.15"],"code":"The name evolved through several candidates during design:"},{"id":"/root/children/15/children/0","type":"text","loc":{"start":2167,"end":2225,"line":{"s":64,"e":64,"code":["The name evolved through several candidates during design:"]},"column":{"s":0,"e":58}},"dim":["","paragraph.15","text.0"],"code":"The name evolved through several candidates during design:"},{"id":"/root/children/16","type":"list","loc":{"start":2227,"end":2574,"line":{"s":66,"e":71,"code":["- **expansion** — suggests something that unfolds when activated","- **diversion** — content that diverts from normal output flow","- **fragment instruction** — a fragment that carries an instruction","- **generator** — evokes generating content from the label","- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"","  and \"construction\""]},"column":{"s":0,"e":20}},"dim":["","list.16"],"code":"- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"","symbName":"list","symbRange":[2576,2718],"symbRangeL":[66,77],"outerCode":"- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals","outerHtml":"<ul><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>"},{"id":"/root/children/16/children/0","type":"listItem","loc":{"start":2227,"end":2291,"line":{"s":66,"e":66,"code":["- **expansion** — suggests something that unfolds when activated"]},"column":{"s":0,"e":64}},"dim":["","list.16","listItem.0"],"code":"- **expansion** — suggests something that unfolds when activated"},{"id":"/root/children/16/children/0/children/0","type":"paragraph","loc":{"start":2229,"end":2291,"line":{"s":66,"e":66,"code":["- **expansion** — suggests something that unfolds when activated"]},"column":{"s":2,"e":64}},"dim":["","list.16","listItem.0","paragraph.0"],"code":"**expansion** — suggests something that unfolds when activated"},{"id":"/root/children/16/children/0/children/0/children/0","type":"strong","loc":{"start":2229,"end":2242,"line":{"s":66,"e":66,"code":["- **expansion** — suggests something that unfolds when activated"]},"column":{"s":2,"e":15}},"dim":["","list.16","listItem.0","paragraph.0","strong.0"],"code":"**expansion**"},{"id":"/root/children/16/children/0/children/0/children/0/children/0","type":"text","loc":{"start":2231,"end":2240,"line":{"s":66,"e":66,"code":["- **expansion** — suggests something that unfolds when activated"]},"column":{"s":4,"e":13}},"dim":["","list.16","listItem.0","paragraph.0","strong.0","text.0"],"code":"expansion"},{"id":"/root/children/16/children/0/children/0/children/1","type":"text","loc":{"start":2242,"end":2291,"line":{"s":66,"e":66,"code":["- **expansion** — suggests something that unfolds when activated"]},"column":{"s":15,"e":64}},"dim":["","list.16","listItem.0","paragraph.0","text.1"],"code":" — suggests something that unfolds when activated"},{"id":"/root/children/16/children/1","type":"listItem","loc":{"start":2292,"end":2354,"line":{"s":67,"e":67,"code":["- **diversion** — content that diverts from normal output flow"]},"column":{"s":0,"e":62}},"dim":["","list.16","listItem.1"],"code":"- **diversion** — content that diverts from normal output flow"},{"id":"/root/children/16/children/1/children/0","type":"paragraph","loc":{"start":2294,"end":2354,"line":{"s":67,"e":67,"code":["- **diversion** — content that diverts from normal output flow"]},"column":{"s":2,"e":62}},"dim":["","list.16","listItem.1","paragraph.0"],"code":"**diversion** — content that diverts from normal output flow"},{"id":"/root/children/16/children/1/children/0/children/0","type":"strong","loc":{"start":2294,"end":2307,"line":{"s":67,"e":67,"code":["- **diversion** — content that diverts from normal output flow"]},"column":{"s":2,"e":15}},"dim":["","list.16","listItem.1","paragraph.0","strong.0"],"code":"**diversion**"},{"id":"/root/children/16/children/1/children/0/children/0/children/0","type":"text","loc":{"start":2296,"end":2305,"line":{"s":67,"e":67,"code":["- **diversion** — content that diverts from normal output flow"]},"column":{"s":4,"e":13}},"dim":["","list.16","listItem.1","paragraph.0","strong.0","text.0"],"code":"diversion"},{"id":"/root/children/16/children/1/children/0/children/1","type":"text","loc":{"start":2307,"end":2354,"line":{"s":67,"e":67,"code":["- **diversion** — content that diverts from normal output flow"]},"column":{"s":15,"e":62}},"dim":["","list.16","listItem.1","paragraph.0","text.1"],"code":" — content that diverts from normal output flow"},{"id":"/root/children/16/children/2","type":"listItem","loc":{"start":2355,"end":2422,"line":{"s":68,"e":68,"code":["- **fragment instruction** — a fragment that carries an instruction"]},"column":{"s":0,"e":67}},"dim":["","list.16","listItem.2"],"code":"- **fragment instruction** — a fragment that carries an instruction"},{"id":"/root/children/16/children/2/children/0","type":"paragraph","loc":{"start":2357,"end":2422,"line":{"s":68,"e":68,"code":["- **fragment instruction** — a fragment that carries an instruction"]},"column":{"s":2,"e":67}},"dim":["","list.16","listItem.2","paragraph.0"],"code":"**fragment instruction** — a fragment that carries an instruction"},{"id":"/root/children/16/children/2/children/0/children/0","type":"strong","loc":{"start":2357,"end":2381,"line":{"s":68,"e":68,"code":["- **fragment instruction** — a fragment that carries an instruction"]},"column":{"s":2,"e":26}},"dim":["","list.16","listItem.2","paragraph.0","strong.0"],"code":"**fragment instruction**"},{"id":"/root/children/16/children/2/children/0/children/0/children/0","type":"text","loc":{"start":2359,"end":2379,"line":{"s":68,"e":68,"code":["- **fragment instruction** — a fragment that carries an instruction"]},"column":{"s":4,"e":24}},"dim":["","list.16","listItem.2","paragraph.0","strong.0","text.0"],"code":"fragment instruction"},{"id":"/root/children/16/children/2/children/0/children/1","type":"text","loc":{"start":2381,"end":2422,"line":{"s":68,"e":68,"code":["- **fragment instruction** — a fragment that carries an instruction"]},"column":{"s":26,"e":67}},"dim":["","list.16","listItem.2","paragraph.0","text.1"],"code":" — a fragment that carries an instruction"},{"id":"/root/children/16/children/3","type":"listItem","loc":{"start":2423,"end":2481,"line":{"s":69,"e":69,"code":["- **generator** — evokes generating content from the label"]},"column":{"s":0,"e":58}},"dim":["","list.16","listItem.3"],"code":"- **generator** — evokes generating content from the label"},{"id":"/root/children/16/children/3/children/0","type":"paragraph","loc":{"start":2425,"end":2481,"line":{"s":69,"e":69,"code":["- **generator** — evokes generating content from the label"]},"column":{"s":2,"e":58}},"dim":["","list.16","listItem.3","paragraph.0"],"code":"**generator** — evokes generating content from the label"},{"id":"/root/children/16/children/3/children/0/children/0","type":"strong","loc":{"start":2425,"end":2438,"line":{"s":69,"e":69,"code":["- **generator** — evokes generating content from the label"]},"column":{"s":2,"e":15}},"dim":["","list.16","listItem.3","paragraph.0","strong.0"],"code":"**generator**"},{"id":"/root/children/16/children/3/children/0/children/0/children/0","type":"text","loc":{"start":2427,"end":2436,"line":{"s":69,"e":69,"code":["- **generator** — evokes generating content from the label"]},"column":{"s":4,"e":13}},"dim":["","list.16","listItem.3","paragraph.0","strong.0","text.0"],"code":"generator"},{"id":"/root/children/16/children/3/children/0/children/1","type":"text","loc":{"start":2438,"end":2481,"line":{"s":69,"e":69,"code":["- **generator** — evokes generating content from the label"]},"column":{"s":15,"e":58}},"dim":["","list.16","listItem.3","paragraph.0","text.1"],"code":" — evokes generating content from the label"},{"id":"/root/children/16/children/4","type":"listItem","loc":{"start":2482,"end":2574,"line":{"s":70,"e":71,"code":["- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"","  and \"construction\""]},"column":{"s":0,"e":20}},"dim":["","list.16","listItem.4"],"code":"- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\""},{"id":"/root/children/16/children/4/children/0","type":"paragraph","loc":{"start":2484,"end":2574,"line":{"s":70,"e":71,"code":["- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"","  and \"construction\""]},"column":{"s":2,"e":20}},"dim":["","list.16","listItem.4","paragraph.0"],"code":"**extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\""},{"id":"/root/children/16/children/4/children/0/children/0","type":"strong","loc":{"start":2484,"end":2498,"line":{"s":70,"e":70,"code":["- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\""]},"column":{"s":2,"e":16}},"dim":["","list.16","listItem.4","paragraph.0","strong.0"],"code":"**extruction**"},{"id":"/root/children/16/children/4/children/0/children/0/children/0","type":"text","loc":{"start":2486,"end":2496,"line":{"s":70,"e":70,"code":["- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\""]},"column":{"s":4,"e":14}},"dim":["","list.16","listItem.4","paragraph.0","strong.0","text.0"],"code":"extruction"},{"id":"/root/children/16/children/4/children/0/children/1","type":"text","loc":{"start":2498,"end":2574,"line":{"s":70,"e":71,"code":["- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"","  and \"construction\""]},"column":{"s":16,"e":20}},"dim":["","list.16","listItem.4","paragraph.0","text.1"],"code":" — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\""},{"id":"/root/children/17","type":"paragraph","loc":{"start":2576,"end":2708,"line":{"s":73,"e":74,"code":["Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,","portal, embed, injection, graft, splice, yield, emit, render."]},"column":{"s":0,"e":61}},"dim":["","paragraph.17"],"code":"Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render."},{"id":"/root/children/17/children/0","type":"text","loc":{"start":2576,"end":2708,"line":{"s":73,"e":74,"code":["Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,","portal, embed, injection, graft, splice, yield, emit, render."]},"column":{"s":0,"e":61}},"dim":["","paragraph.17","text.0"],"code":"Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render."},{"id":"/root/children/18","type":"heading","loc":{"start":2710,"end":2718,"line":{"s":76,"e":76,"code":["## Goals"]},"column":{"s":0,"e":8}},"dim":["","heading.18"],"code":"## Goals","symbName":"heading","symbRange":[2720,3051],"symbRangeL":[76,84],"outerCode":"\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports","outerHtml":"\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>"},{"id":"/root/children/18/children/0","type":"text","loc":{"start":2713,"end":2718,"line":{"s":76,"e":76,"code":["## Goals"]},"column":{"s":3,"e":8}},"dim":["","heading.18","text.0"],"code":"Goals"},{"id":"/root/children/19","type":"list","loc":{"start":2720,"end":3051,"line":{"s":78,"e":83,"code":["- Markdown is the surface language","- `# ${...}` headings are **extructions** — labeled markers, filtered","  from output; bodies use ` ```javascript ` code blocks for eval","- **Lazy by default**: only process what the consumer pulls","- **Pure functions throughout**: all dependencies are explicit arguments,","  never closed-over imports"]},"column":{"s":0,"e":27}},"dim":["","list.19"],"code":"- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports","symbName":"list","symbRange":[3053,3641],"symbRangeL":[78,112],"outerCode":"- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):","outerHtml":"<ul><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>"},{"id":"/root/children/19/children/0","type":"listItem","loc":{"start":2720,"end":2754,"line":{"s":78,"e":78,"code":["- Markdown is the surface language"]},"column":{"s":0,"e":34}},"dim":["","list.19","listItem.0"],"code":"- Markdown is the surface language"},{"id":"/root/children/19/children/0/children/0","type":"paragraph","loc":{"start":2722,"end":2754,"line":{"s":78,"e":78,"code":["- Markdown is the surface language"]},"column":{"s":2,"e":34}},"dim":["","list.19","listItem.0","paragraph.0"],"code":"Markdown is the surface language"},{"id":"/root/children/19/children/0/children/0/children/0","type":"text","loc":{"start":2722,"end":2754,"line":{"s":78,"e":78,"code":["- Markdown is the surface language"]},"column":{"s":2,"e":34}},"dim":["","list.19","listItem.0","paragraph.0","text.0"],"code":"Markdown is the surface language"},{"id":"/root/children/19/children/1","type":"listItem","loc":{"start":2755,"end":2889,"line":{"s":79,"e":80,"code":["- `# ${...}` headings are **extructions** — labeled markers, filtered","  from output; bodies use ` ```javascript ` code blocks for eval"]},"column":{"s":0,"e":64}},"dim":["","list.19","listItem.1"],"code":"- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval"},{"id":"/root/children/19/children/1/children/0","type":"paragraph","loc":{"start":2757,"end":2889,"line":{"s":79,"e":80,"code":["- `# ${...}` headings are **extructions** — labeled markers, filtered","  from output; bodies use ` ```javascript ` code blocks for eval"]},"column":{"s":2,"e":64}},"dim":["","list.19","listItem.1","paragraph.0"],"code":"`# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval"},{"id":"/root/children/19/children/1/children/0/children/0","type":"inlineCode","loc":{"start":2757,"end":2767,"line":{"s":79,"e":79,"code":["- `# ${...}` headings are **extructions** — labeled markers, filtered"]},"column":{"s":2,"e":12}},"dim":["","list.19","listItem.1","paragraph.0","inlineCode.0"],"code":"`# ${...}`"},{"id":"/root/children/19/children/1/children/0/children/1","type":"text","loc":{"start":2767,"end":2781,"line":{"s":79,"e":79,"code":["- `# ${...}` headings are **extructions** — labeled markers, filtered"]},"column":{"s":12,"e":26}},"dim":["","list.19","listItem.1","paragraph.0","text.1"],"code":" headings are "},{"id":"/root/children/19/children/1/children/0/children/2","type":"strong","loc":{"start":2781,"end":2796,"line":{"s":79,"e":79,"code":["- `# ${...}` headings are **extructions** — labeled markers, filtered"]},"column":{"s":26,"e":41}},"dim":["","list.19","listItem.1","paragraph.0","strong.2"],"code":"**extructions**"},{"id":"/root/children/19/children/1/children/0/children/2/children/0","type":"text","loc":{"start":2783,"end":2794,"line":{"s":79,"e":79,"code":["- `# ${...}` headings are **extructions** — labeled markers, filtered"]},"column":{"s":28,"e":39}},"dim":["","list.19","listItem.1","paragraph.0","strong.2","text.0"],"code":"extructions"},{"id":"/root/children/19/children/1/children/0/children/3","type":"text","loc":{"start":2796,"end":2851,"line":{"s":79,"e":80,"code":["- `# ${...}` headings are **extructions** — labeled markers, filtered","  from output; bodies use ` ```javascript ` code blocks for eval"]},"column":{"s":41,"e":26}},"dim":["","list.19","listItem.1","paragraph.0","text.3"],"code":" — labeled markers, filtered\n  from output; bodies use "},{"id":"/root/children/19/children/1/children/0/children/4","type":"inlineCode","loc":{"start":2851,"end":2868,"line":{"s":80,"e":80,"code":["  from output; bodies use ` ```javascript ` code blocks for eval"]},"column":{"s":26,"e":43}},"dim":["","list.19","listItem.1","paragraph.0","inlineCode.4"],"code":"` ```javascript `"},{"id":"/root/children/19/children/1/children/0/children/5","type":"text","loc":{"start":2868,"end":2889,"line":{"s":80,"e":80,"code":["  from output; bodies use ` ```javascript ` code blocks for eval"]},"column":{"s":43,"e":64}},"dim":["","list.19","listItem.1","paragraph.0","text.5"],"code":" code blocks for eval"},{"id":"/root/children/19/children/2","type":"listItem","loc":{"start":2890,"end":2949,"line":{"s":81,"e":81,"code":["- **Lazy by default**: only process what the consumer pulls"]},"column":{"s":0,"e":59}},"dim":["","list.19","listItem.2"],"code":"- **Lazy by default**: only process what the consumer pulls"},{"id":"/root/children/19/children/2/children/0","type":"paragraph","loc":{"start":2892,"end":2949,"line":{"s":81,"e":81,"code":["- **Lazy by default**: only process what the consumer pulls"]},"column":{"s":2,"e":59}},"dim":["","list.19","listItem.2","paragraph.0"],"code":"**Lazy by default**: only process what the consumer pulls"},{"id":"/root/children/19/children/2/children/0/children/0","type":"strong","loc":{"start":2892,"end":2911,"line":{"s":81,"e":81,"code":["- **Lazy by default**: only process what the consumer pulls"]},"column":{"s":2,"e":21}},"dim":["","list.19","listItem.2","paragraph.0","strong.0"],"code":"**Lazy by default**"},{"id":"/root/children/19/children/2/children/0/children/0/children/0","type":"text","loc":{"start":2894,"end":2909,"line":{"s":81,"e":81,"code":["- **Lazy by default**: only process what the consumer pulls"]},"column":{"s":4,"e":19}},"dim":["","list.19","listItem.2","paragraph.0","strong.0","text.0"],"code":"Lazy by default"},{"id":"/root/children/19/children/2/children/0/children/1","type":"text","loc":{"start":2911,"end":2949,"line":{"s":81,"e":81,"code":["- **Lazy by default**: only process what the consumer pulls"]},"column":{"s":21,"e":59}},"dim":["","list.19","listItem.2","paragraph.0","text.1"],"code":": only process what the consumer pulls"},{"id":"/root/children/19/children/3","type":"listItem","loc":{"start":2950,"end":3051,"line":{"s":82,"e":83,"code":["- **Pure functions throughout**: all dependencies are explicit arguments,","  never closed-over imports"]},"column":{"s":0,"e":27}},"dim":["","list.19","listItem.3"],"code":"- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports"},{"id":"/root/children/19/children/3/children/0","type":"paragraph","loc":{"start":2952,"end":3051,"line":{"s":82,"e":83,"code":["- **Pure functions throughout**: all dependencies are explicit arguments,","  never closed-over imports"]},"column":{"s":2,"e":27}},"dim":["","list.19","listItem.3","paragraph.0"],"code":"**Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports"},{"id":"/root/children/19/children/3/children/0/children/0","type":"strong","loc":{"start":2952,"end":2981,"line":{"s":82,"e":82,"code":["- **Pure functions throughout**: all dependencies are explicit arguments,"]},"column":{"s":2,"e":31}},"dim":["","list.19","listItem.3","paragraph.0","strong.0"],"code":"**Pure functions throughout**"},{"id":"/root/children/19/children/3/children/0/children/0/children/0","type":"text","loc":{"start":2954,"end":2979,"line":{"s":82,"e":82,"code":["- **Pure functions throughout**: all dependencies are explicit arguments,"]},"column":{"s":4,"e":29}},"dim":["","list.19","listItem.3","paragraph.0","strong.0","text.0"],"code":"Pure functions throughout"},{"id":"/root/children/19/children/3/children/0/children/1","type":"text","loc":{"start":2981,"end":3051,"line":{"s":82,"e":83,"code":["- **Pure functions throughout**: all dependencies are explicit arguments,","  never closed-over imports"]},"column":{"s":31,"e":27}},"dim":["","list.19","listItem.3","paragraph.0","text.1"],"code":": all dependencies are explicit arguments,\n  never closed-over imports"},{"id":"/root/children/20","type":"heading","loc":{"start":3053,"end":3071,"line":{"s":85,"e":85,"code":["## mdt as Markdown"]},"column":{"s":0,"e":18}},"dim":["","heading.20"],"code":"## mdt as Markdown","symbName":"heading","symbRange":[3073,3268],"symbRangeL":[85,91],"outerCode":"\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.","outerHtml":"\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>"},{"id":"/root/children/20/children/0","type":"text","loc":{"start":3056,"end":3071,"line":{"s":85,"e":85,"code":["## mdt as Markdown"]},"column":{"s":3,"e":18}},"dim":["","heading.20","text.0"],"code":"mdt as Markdown"},{"id":"/root/children/21","type":"paragraph","loc":{"start":3073,"end":3268,"line":{"s":87,"e":90,"code":["Every `.mdd` file is also valid `.md`.","Extructions (`# ${label}`) render as ordinary visible headings.","Standard markdown renderers see no special syntax — the mdt semantics are","invisible to them."]},"column":{"s":0,"e":18}},"dim":["","paragraph.21"],"code":"Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them."},{"id":"/root/children/21/children/0","type":"text","loc":{"start":3073,"end":3079,"line":{"s":87,"e":87,"code":["Every `.mdd` file is also valid `.md`."]},"column":{"s":0,"e":6}},"dim":["","paragraph.21","text.0"],"code":"Every "},{"id":"/root/children/21/children/1","type":"inlineCode","loc":{"start":3079,"end":3085,"line":{"s":87,"e":87,"code":["Every `.mdd` file is also valid `.md`."]},"column":{"s":6,"e":12}},"dim":["","paragraph.21","inlineCode.1"],"code":"`.mdd`"},{"id":"/root/children/21/children/2","type":"text","loc":{"start":3085,"end":3105,"line":{"s":87,"e":87,"code":["Every `.mdd` file is also valid `.md`."]},"column":{"s":12,"e":32}},"dim":["","paragraph.21","text.2"],"code":" file is also valid "},{"id":"/root/children/21/children/3","type":"inlineCode","loc":{"start":3105,"end":3110,"line":{"s":87,"e":87,"code":["Every `.mdd` file is also valid `.md`."]},"column":{"s":32,"e":37}},"dim":["","paragraph.21","inlineCode.3"],"code":"`.md`"},{"id":"/root/children/21/children/4","type":"text","loc":{"start":3110,"end":3125,"line":{"s":87,"e":88,"code":["Every `.mdd` file is also valid `.md`.","Extructions (`# ${label}`) render as ordinary visible headings."]},"column":{"s":37,"e":13}},"dim":["","paragraph.21","text.4"],"code":".\nExtructions ("},{"id":"/root/children/21/children/5","type":"inlineCode","loc":{"start":3125,"end":3137,"line":{"s":88,"e":88,"code":["Extructions (`# ${label}`) render as ordinary visible headings."]},"column":{"s":13,"e":25}},"dim":["","paragraph.21","inlineCode.5"],"code":"`# ${label}`"},{"id":"/root/children/21/children/6","type":"text","loc":{"start":3137,"end":3268,"line":{"s":88,"e":90,"code":["Extructions (`# ${label}`) render as ordinary visible headings.","Standard markdown renderers see no special syntax — the mdt semantics are","invisible to them."]},"column":{"s":25,"e":18}},"dim":["","paragraph.21","text.6"],"code":") render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them."},{"id":"/root/children/22","type":"heading","loc":{"start":3270,"end":3282,"line":{"s":92,"e":92,"code":["## compile()"]},"column":{"s":0,"e":12}},"dim":["","heading.22"],"code":"## compile()","symbName":"heading","symbRange":[3285,3861],"symbRangeL":[92,119],"outerCode":"\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.","outerHtml":"\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>"},{"id":"/root/children/22/children/0","type":"text","loc":{"start":3273,"end":3282,"line":{"s":92,"e":92,"code":["## compile()"]},"column":{"s":3,"e":12}},"dim":["","heading.22","text.0"],"code":"compile()"},{"id":"/root/children/23","type":"code","loc":{"start":3285,"end":3328,"line":{"s":95,"e":97,"code":["```","compile(mdtMd, { remark }) → Runner","```"]},"column":{"s":0,"e":3}},"dim":["","code.23"],"code":"```\ncompile(mdtMd, { remark }) → Runner\n```","symbName":"code","symbRange":[3330,3465],"symbRangeL":[null,103],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n"},{"id":"/root/children/24","type":"paragraph","loc":{"start":3330,"end":3465,"line":{"s":99,"e":101,"code":["Single entry point.","Takes raw mdt markdown text and a remark instance (for `.parse()`).","Returns a `Runner` — no evaluation happens yet."]},"column":{"s":0,"e":47}},"dim":["","paragraph.24"],"code":"Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet."},{"id":"/root/children/24/children/0","type":"text","loc":{"start":3330,"end":3405,"line":{"s":99,"e":100,"code":["Single entry point.","Takes raw mdt markdown text and a remark instance (for `.parse()`)."]},"column":{"s":0,"e":55}},"dim":["","paragraph.24","text.0"],"code":"Single entry point.\nTakes raw mdt markdown text and a remark instance (for "},{"id":"/root/children/24/children/1","type":"inlineCode","loc":{"start":3405,"end":3415,"line":{"s":100,"e":100,"code":["Takes raw mdt markdown text and a remark instance (for `.parse()`)."]},"column":{"s":55,"e":65}},"dim":["","paragraph.24","inlineCode.1"],"code":"`.parse()`"},{"id":"/root/children/24/children/2","type":"text","loc":{"start":3415,"end":3428,"line":{"s":100,"e":101,"code":["Takes raw mdt markdown text and a remark instance (for `.parse()`).","Returns a `Runner` — no evaluation happens yet."]},"column":{"s":65,"e":10}},"dim":["","paragraph.24","text.2"],"code":").\nReturns a "},{"id":"/root/children/24/children/3","type":"inlineCode","loc":{"start":3428,"end":3436,"line":{"s":101,"e":101,"code":["Returns a `Runner` — no evaluation happens yet."]},"column":{"s":10,"e":18}},"dim":["","paragraph.24","inlineCode.3"],"code":"`Runner`"},{"id":"/root/children/24/children/4","type":"text","loc":{"start":3436,"end":3465,"line":{"s":101,"e":101,"code":["Returns a `Runner` — no evaluation happens yet."]},"column":{"s":18,"e":47}},"dim":["","paragraph.24","text.4"],"code":" — no evaluation happens yet."},{"id":"/root/children/25","type":"code","loc":{"start":3468,"end":3592,"line":{"s":104,"e":109,"code":["```","import { compile } from './mdt/mdt.js'","import { remark } from 'remark'","","const runner = compile(sourceMd, { remark })","```"]},"column":{"s":0,"e":3}},"dim":["","code.25"],"code":"```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```","symbName":"code","symbRange":[3594,3872],"symbRangeL":[null,122],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n"},{"id":"/root/children/26","type":"paragraph","loc":{"start":3594,"end":3641,"line":{"s":111,"e":111,"code":["**Compile-time errors** (thrown synchronously):"]},"column":{"s":0,"e":47}},"dim":["","paragraph.26"],"code":"**Compile-time errors** (thrown synchronously):"},{"id":"/root/children/26/children/0","type":"strong","loc":{"start":3594,"end":3617,"line":{"s":111,"e":111,"code":["**Compile-time errors** (thrown synchronously):"]},"column":{"s":0,"e":23}},"dim":["","paragraph.26","strong.0"],"code":"**Compile-time errors**"},{"id":"/root/children/26/children/0/children/0","type":"text","loc":{"start":3596,"end":3615,"line":{"s":111,"e":111,"code":["**Compile-time errors** (thrown synchronously):"]},"column":{"s":2,"e":21}},"dim":["","paragraph.26","strong.0","text.0"],"code":"Compile-time errors"},{"id":"/root/children/26/children/1","type":"text","loc":{"start":3617,"end":3641,"line":{"s":111,"e":111,"code":["**Compile-time errors** (thrown synchronously):"]},"column":{"s":23,"e":47}},"dim":["","paragraph.26","text.1"],"code":" (thrown synchronously):"},{"id":"/root/children/27","type":"list","loc":{"start":3643,"end":3688,"line":{"s":113,"e":113,"code":["- Unparseable markdown (remark parse failure)"]},"column":{"s":0,"e":45}},"dim":["","list.27"],"code":"- Unparseable markdown (remark parse failure)","symbName":"list","symbRange":[3690,4430],"symbRangeL":[113,145],"outerCode":"\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:","outerHtml":"\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>"},{"id":"/root/children/27/children/0","type":"listItem","loc":{"start":3643,"end":3688,"line":{"s":113,"e":113,"code":["- Unparseable markdown (remark parse failure)"]},"column":{"s":0,"e":45}},"dim":["","list.27","listItem.0"],"code":"- Unparseable markdown (remark parse failure)"},{"id":"/root/children/27/children/0/children/0","type":"paragraph","loc":{"start":3645,"end":3688,"line":{"s":113,"e":113,"code":["- Unparseable markdown (remark parse failure)"]},"column":{"s":2,"e":45}},"dim":["","list.27","listItem.0","paragraph.0"],"code":"Unparseable markdown (remark parse failure)"},{"id":"/root/children/27/children/0/children/0/children/0","type":"text","loc":{"start":3645,"end":3688,"line":{"s":113,"e":113,"code":["- Unparseable markdown (remark parse failure)"]},"column":{"s":2,"e":45}},"dim":["","list.27","listItem.0","paragraph.0","text.0"],"code":"Unparseable markdown (remark parse failure)"},{"id":"/root/children/28","type":"paragraph","loc":{"start":3690,"end":3861,"line":{"s":115,"e":118,"code":["During compilation, headings whose text starts with `${` are marked as","extructions.","They are tracked separately but","no transform is applied — the remark AST is kept as-is."]},"column":{"s":0,"e":55}},"dim":["","paragraph.28"],"code":"During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is."},{"id":"/root/children/28/children/0","type":"text","loc":{"start":3690,"end":3742,"line":{"s":115,"e":115,"code":["During compilation, headings whose text starts with `${` are marked as"]},"column":{"s":0,"e":52}},"dim":["","paragraph.28","text.0"],"code":"During compilation, headings whose text starts with "},{"id":"/root/children/28/children/1","type":"inlineCode","loc":{"start":3742,"end":3746,"line":{"s":115,"e":115,"code":["During compilation, headings whose text starts with `${` are marked as"]},"column":{"s":52,"e":56}},"dim":["","paragraph.28","inlineCode.1"],"code":"`${`"},{"id":"/root/children/28/children/2","type":"text","loc":{"start":3746,"end":3861,"line":{"s":115,"e":118,"code":["During compilation, headings whose text starts with `${` are marked as","extructions.","They are tracked separately but","no transform is applied — the remark AST is kept as-is."]},"column":{"s":56,"e":55}},"dim":["","paragraph.28","text.2"],"code":" are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is."},{"id":"/root/children/29","type":"heading","loc":{"start":3863,"end":3872,"line":{"s":120,"e":120,"code":["## Runner"]},"column":{"s":0,"e":9}},"dim":["","heading.29"],"code":"## Runner","symbName":"heading","symbRange":[3875,5314],"symbRangeL":[120,160],"outerCode":"\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.","outerHtml":"\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>"},{"id":"/root/children/29/children/0","type":"text","loc":{"start":3866,"end":3872,"line":{"s":120,"e":120,"code":["## Runner"]},"column":{"s":3,"e":9}},"dim":["","heading.29","text.0"],"code":"Runner"},{"id":"/root/children/30","type":"code","loc":{"start":3875,"end":3916,"line":{"s":123,"e":125,"code":["```","runner(context, opts?) → Document","```"]},"column":{"s":0,"e":3}},"dim":["","code.30"],"code":"```\nrunner(context, opts?) → Document\n```","symbName":"code","symbRange":[3918,4162],"symbRangeL":[null,134],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n"},{"id":"/root/children/31","type":"paragraph","loc":{"start":3918,"end":4123,"line":{"s":127,"e":130,"code":["The runner is a function.","Call it with context and options to get a **Document** — the entry point for","navigating the document tree.","No processing happens until you pull from the iterable or call navigate."]},"column":{"s":0,"e":72}},"dim":["","paragraph.31"],"code":"The runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate."},{"id":"/root/children/31/children/0","type":"text","loc":{"start":3918,"end":3986,"line":{"s":127,"e":128,"code":["The runner is a function.","Call it with context and options to get a **Document** — the entry point for"]},"column":{"s":0,"e":42}},"dim":["","paragraph.31","text.0"],"code":"The runner is a function.\nCall it with context and options to get a "},{"id":"/root/children/31/children/1","type":"strong","loc":{"start":3986,"end":3998,"line":{"s":128,"e":128,"code":["Call it with context and options to get a **Document** — the entry point for"]},"column":{"s":42,"e":54}},"dim":["","paragraph.31","strong.1"],"code":"**Document**"},{"id":"/root/children/31/children/1/children/0","type":"text","loc":{"start":3988,"end":3996,"line":{"s":128,"e":128,"code":["Call it with context and options to get a **Document** — the entry point for"]},"column":{"s":44,"e":52}},"dim":["","paragraph.31","strong.1","text.0"],"code":"Document"},{"id":"/root/children/31/children/2","type":"text","loc":{"start":3998,"end":4123,"line":{"s":128,"e":130,"code":["Call it with context and options to get a **Document** — the entry point for","navigating the document tree.","No processing happens until you pull from the iterable or call navigate."]},"column":{"s":54,"e":72}},"dim":["","paragraph.31","text.2"],"code":" — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate."},{"id":"/root/children/32","type":"paragraph","loc":{"start":4125,"end":4162,"line":{"s":132,"e":132,"code":["`opts` carries run-time dependencies:"]},"column":{"s":0,"e":37}},"dim":["","paragraph.32"],"code":"`opts` carries run-time dependencies:"},{"id":"/root/children/32/children/0","type":"inlineCode","loc":{"start":4125,"end":4131,"line":{"s":132,"e":132,"code":["`opts` carries run-time dependencies:"]},"column":{"s":0,"e":6}},"dim":["","paragraph.32","inlineCode.0"],"code":"`opts`"},{"id":"/root/children/32/children/1","type":"text","loc":{"start":4131,"end":4162,"line":{"s":132,"e":132,"code":["`opts` carries run-time dependencies:"]},"column":{"s":6,"e":37}},"dim":["","paragraph.32","text.1"],"code":" carries run-time dependencies:"},{"id":"/root/children/33","type":"code","loc":{"start":4165,"end":4271,"line":{"s":135,"e":139,"code":["```","opts = {","  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')","}","```"]},"column":{"s":0,"e":3}},"dim":["","code.33"],"code":"```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```","symbName":"code","symbRange":[4273,5455],"symbRangeL":[null,166],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n"},{"id":"/root/children/34","type":"paragraph","loc":{"start":4273,"end":4409,"line":{"s":141,"e":142,"code":["`sanitizeName` defaults to the function shown (lowercase, non-word chars to","`-`, leading/trailing dashes trimmed). Callers can override."]},"column":{"s":0,"e":60}},"dim":["","paragraph.34"],"code":"`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override."},{"id":"/root/children/34/children/0","type":"inlineCode","loc":{"start":4273,"end":4287,"line":{"s":141,"e":141,"code":["`sanitizeName` defaults to the function shown (lowercase, non-word chars to"]},"column":{"s":0,"e":14}},"dim":["","paragraph.34","inlineCode.0"],"code":"`sanitizeName`"},{"id":"/root/children/34/children/1","type":"text","loc":{"start":4287,"end":4349,"line":{"s":141,"e":142,"code":["`sanitizeName` defaults to the function shown (lowercase, non-word chars to","`-`, leading/trailing dashes trimmed). Callers can override."]},"column":{"s":14,"e":0}},"dim":["","paragraph.34","text.1"],"code":" defaults to the function shown (lowercase, non-word chars to\n"},{"id":"/root/children/34/children/2","type":"inlineCode","loc":{"start":4349,"end":4352,"line":{"s":142,"e":142,"code":["`-`, leading/trailing dashes trimmed). Callers can override."]},"column":{"s":0,"e":3}},"dim":["","paragraph.34","inlineCode.2"],"code":"`-`"},{"id":"/root/children/34/children/3","type":"text","loc":{"start":4352,"end":4409,"line":{"s":142,"e":142,"code":["`-`, leading/trailing dashes trimmed). Callers can override."]},"column":{"s":3,"e":60}},"dim":["","paragraph.34","text.3"],"code":", leading/trailing dashes trimmed). Callers can override."},{"id":"/root/children/35","type":"paragraph","loc":{"start":4411,"end":4430,"line":{"s":144,"e":144,"code":["`opts.loadRefBody`:"]},"column":{"s":0,"e":19}},"dim":["","paragraph.35"],"code":"`opts.loadRefBody`:"},{"id":"/root/children/35/children/0","type":"inlineCode","loc":{"start":4411,"end":4429,"line":{"s":144,"e":144,"code":["`opts.loadRefBody`:"]},"column":{"s":0,"e":18}},"dim":["","paragraph.35","inlineCode.0"],"code":"`opts.loadRefBody`"},{"id":"/root/children/35/children/1","type":"text","loc":{"start":4429,"end":4430,"line":{"s":144,"e":144,"code":["`opts.loadRefBody`:"]},"column":{"s":18,"e":19}},"dim":["","paragraph.35","text.1"],"code":":"},{"id":"/root/children/36","type":"list","loc":{"start":4432,"end":5314,"line":{"s":146,"e":159,"code":["- `async (item, targetDepth) → string` — fetches the body markdown for","  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`","  is iterated by the consumer.","- `targetDepth` is the heading depth at which the Fragment's root","  heading is emitted; the returned body must have its own root heading","  stripped and its nested subheadings shifted so root+1 lands at","  `targetDepth+1`, root+2 at `targetDepth+2`, etc.","- App integration: compose existing `loadFragment(...)` +","  `relevelFragment(text, targetDepth - 1)` (bare import from","  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the","  root heading. `relevelFragment(text, N)` puts the source root at","  depth `N+1`, so passing `targetDepth - 1` puts the root at","  `targetDepth` — after the root-strip, the source's root+1 headings","  are what's left, correctly landing at `targetDepth+1`."]},"column":{"s":0,"e":56}},"dim":["","list.36"],"code":"- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.","symbName":"list","symbRange":[5316,5664],"symbRangeL":[146,173],"outerCode":"  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```","outerHtml":"<p>  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</p><ul><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>"},{"id":"/root/children/36/children/0","type":"listItem","loc":{"start":4432,"end":4616,"line":{"s":146,"e":148,"code":["- `async (item, targetDepth) → string` — fetches the body markdown for","  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`","  is iterated by the consumer."]},"column":{"s":0,"e":30}},"dim":["","list.36","listItem.0"],"code":"- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer."},{"id":"/root/children/36/children/0/children/0","type":"paragraph","loc":{"start":4434,"end":4616,"line":{"s":146,"e":148,"code":["- `async (item, targetDepth) → string` — fetches the body markdown for","  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`","  is iterated by the consumer."]},"column":{"s":2,"e":30}},"dim":["","list.36","listItem.0","paragraph.0"],"code":"`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer."},{"id":"/root/children/36/children/0/children/0/children/0","type":"inlineCode","loc":{"start":4434,"end":4470,"line":{"s":146,"e":146,"code":["- `async (item, targetDepth) → string` — fetches the body markdown for"]},"column":{"s":2,"e":38}},"dim":["","list.36","listItem.0","paragraph.0","inlineCode.0"],"code":"`async (item, targetDepth) → string`"},{"id":"/root/children/36/children/0/children/0/children/1","type":"text","loc":{"start":4470,"end":4509,"line":{"s":146,"e":147,"code":["- `async (item, targetDepth) → string` — fetches the body markdown for","  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`"]},"column":{"s":38,"e":6}},"dim":["","list.36","listItem.0","paragraph.0","text.1"],"code":" — fetches the body markdown for\n  one "},{"id":"/root/children/36/children/0/children/0/children/2","type":"inlineCode","loc":{"start":4509,"end":4530,"line":{"s":147,"e":147,"code":["  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`"]},"column":{"s":6,"e":27}},"dim":["","list.36","listItem.0","paragraph.0","inlineCode.2"],"code":"`insertRefsAsSubtree`"},{"id":"/root/children/36/children/0/children/0/children/3","type":"text","loc":{"start":4530,"end":4575,"line":{"s":147,"e":147,"code":["  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`"]},"column":{"s":27,"e":72}},"dim":["","list.36","listItem.0","paragraph.0","text.3"],"code":" item. Called lazily, only when a Fragment's "},{"id":"/root/children/36/children/0/children/0/children/4","type":"inlineCode","loc":{"start":4575,"end":4585,"line":{"s":147,"e":147,"code":["  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`"]},"column":{"s":72,"e":82}},"dim":["","list.36","listItem.0","paragraph.0","inlineCode.4"],"code":"`expand()`"},{"id":"/root/children/36/children/0/children/0/children/5","type":"text","loc":{"start":4585,"end":4616,"line":{"s":147,"e":148,"code":["  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`","  is iterated by the consumer."]},"column":{"s":82,"e":30}},"dim":["","list.36","listItem.0","paragraph.0","text.5"],"code":"\n  is iterated by the consumer."},{"id":"/root/children/36/children/1","type":"listItem","loc":{"start":4617,"end":4869,"line":{"s":149,"e":152,"code":["- `targetDepth` is the heading depth at which the Fragment's root","  heading is emitted; the returned body must have its own root heading","  stripped and its nested subheadings shifted so root+1 lands at","  `targetDepth+1`, root+2 at `targetDepth+2`, etc."]},"column":{"s":0,"e":50}},"dim":["","list.36","listItem.1"],"code":"- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc."},{"id":"/root/children/36/children/1/children/0","type":"paragraph","loc":{"start":4619,"end":4869,"line":{"s":149,"e":152,"code":["- `targetDepth` is the heading depth at which the Fragment's root","  heading is emitted; the returned body must have its own root heading","  stripped and its nested subheadings shifted so root+1 lands at","  `targetDepth+1`, root+2 at `targetDepth+2`, etc."]},"column":{"s":2,"e":50}},"dim":["","list.36","listItem.1","paragraph.0"],"code":"`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc."},{"id":"/root/children/36/children/1/children/0/children/0","type":"inlineCode","loc":{"start":4619,"end":4632,"line":{"s":149,"e":149,"code":["- `targetDepth` is the heading depth at which the Fragment's root"]},"column":{"s":2,"e":15}},"dim":["","list.36","listItem.1","paragraph.0","inlineCode.0"],"code":"`targetDepth`"},{"id":"/root/children/36/children/1/children/0/children/1","type":"text","loc":{"start":4632,"end":4819,"line":{"s":149,"e":152,"code":["- `targetDepth` is the heading depth at which the Fragment's root","  heading is emitted; the returned body must have its own root heading","  stripped and its nested subheadings shifted so root+1 lands at","  `targetDepth+1`, root+2 at `targetDepth+2`, etc."]},"column":{"s":15,"e":0}},"dim":["","list.36","listItem.1","paragraph.0","text.1"],"code":" is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n"},{"id":"/root/children/36/children/1/children/0/children/2","type":"inlineCode","loc":{"start":4821,"end":4836,"line":{"s":152,"e":152,"code":["  `targetDepth+1`, root+2 at `targetDepth+2`, etc."]},"column":{"s":2,"e":17}},"dim":["","list.36","listItem.1","paragraph.0","inlineCode.2"],"code":"`targetDepth+1`"},{"id":"/root/children/36/children/1/children/0/children/3","type":"text","loc":{"start":4836,"end":4848,"line":{"s":152,"e":152,"code":["  `targetDepth+1`, root+2 at `targetDepth+2`, etc."]},"column":{"s":17,"e":29}},"dim":["","list.36","listItem.1","paragraph.0","text.3"],"code":", root+2 at "},{"id":"/root/children/36/children/1/children/0/children/4","type":"inlineCode","loc":{"start":4848,"end":4863,"line":{"s":152,"e":152,"code":["  `targetDepth+1`, root+2 at `targetDepth+2`, etc."]},"column":{"s":29,"e":44}},"dim":["","list.36","listItem.1","paragraph.0","inlineCode.4"],"code":"`targetDepth+2`"},{"id":"/root/children/36/children/1/children/0/children/5","type":"text","loc":{"start":4863,"end":4869,"line":{"s":152,"e":152,"code":["  `targetDepth+1`, root+2 at `targetDepth+2`, etc."]},"column":{"s":44,"e":50}},"dim":["","list.36","listItem.1","paragraph.0","text.5"],"code":", etc."},{"id":"/root/children/36/children/2","type":"listItem","loc":{"start":4870,"end":5314,"line":{"s":153,"e":159,"code":["- App integration: compose existing `loadFragment(...)` +","  `relevelFragment(text, targetDepth - 1)` (bare import from","  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the","  root heading. `relevelFragment(text, N)` puts the source root at","  depth `N+1`, so passing `targetDepth - 1` puts the root at","  `targetDepth` — after the root-strip, the source's root+1 headings","  are what's left, correctly landing at `targetDepth+1`."]},"column":{"s":0,"e":56}},"dim":["","list.36","listItem.2"],"code":"- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`."},{"id":"/root/children/36/children/2/children/0","type":"paragraph","loc":{"start":4872,"end":5314,"line":{"s":153,"e":159,"code":["- App integration: compose existing `loadFragment(...)` +","  `relevelFragment(text, targetDepth - 1)` (bare import from","  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the","  root heading. `relevelFragment(text, N)` puts the source root at","  depth `N+1`, so passing `targetDepth - 1` puts the root at","  `targetDepth` — after the root-strip, the source's root+1 headings","  are what's left, correctly landing at `targetDepth+1`."]},"column":{"s":2,"e":56}},"dim":["","list.36","listItem.2","paragraph.0"],"code":"App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`."},{"id":"/root/children/36/children/2/children/0/children/0","type":"text","loc":{"start":4872,"end":4906,"line":{"s":153,"e":153,"code":["- App integration: compose existing `loadFragment(...)` +"]},"column":{"s":2,"e":36}},"dim":["","list.36","listItem.2","paragraph.0","text.0"],"code":"App integration: compose existing "},{"id":"/root/children/36/children/2/children/0/children/1","type":"inlineCode","loc":{"start":4906,"end":4925,"line":{"s":153,"e":153,"code":["- App integration: compose existing `loadFragment(...)` +"]},"column":{"s":36,"e":55}},"dim":["","list.36","listItem.2","paragraph.0","inlineCode.1"],"code":"`loadFragment(...)`"},{"id":"/root/children/36/children/2/children/0/children/2","type":"text","loc":{"start":4925,"end":4928,"line":{"s":153,"e":154,"code":["- App integration: compose existing `loadFragment(...)` +","  `relevelFragment(text, targetDepth - 1)` (bare import from"]},"column":{"s":55,"e":0}},"dim":["","list.36","listItem.2","paragraph.0","text.2"],"code":" +\n"},{"id":"/root/children/36/children/2/children/0/children/3","type":"inlineCode","loc":{"start":4930,"end":4970,"line":{"s":154,"e":154,"code":["  `relevelFragment(text, targetDepth - 1)` (bare import from"]},"column":{"s":2,"e":42}},"dim":["","list.36","listItem.2","paragraph.0","inlineCode.3"],"code":"`relevelFragment(text, targetDepth - 1)`"},{"id":"/root/children/36/children/2/children/0/children/4","type":"text","loc":{"start":4970,"end":4989,"line":{"s":154,"e":155,"code":["  `relevelFragment(text, targetDepth - 1)` (bare import from","  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the"]},"column":{"s":42,"e":0}},"dim":["","list.36","listItem.2","paragraph.0","text.4"],"code":" (bare import from\n"},{"id":"/root/children/36/children/2/children/0/children/5","type":"inlineCode","loc":{"start":4991,"end":5008,"line":{"s":155,"e":155,"code":["  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the"]},"column":{"s":2,"e":19}},"dim":["","list.36","listItem.2","paragraph.0","inlineCode.5"],"code":"`player-utils.js`"},{"id":"/root/children/36/children/2/children/0/children/6","type":"text","loc":{"start":5008,"end":5014,"line":{"s":155,"e":155,"code":["  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the"]},"column":{"s":19,"e":25}},"dim":["","list.36","listItem.2","paragraph.0","text.6"],"code":", not "},{"id":"/root/children/36/children/2/children/0/children/7","type":"inlineCode","loc":{"start":5014,"end":5036,"line":{"s":155,"e":155,"code":["  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the"]},"column":{"s":25,"e":47}},"dim":["","list.36","listItem.2","paragraph.0","inlineCode.7"],"code":"`ssss.relevelFragment`"},{"id":"/root/children/36/children/2/children/0/children/8","type":"text","loc":{"start":5036,"end":5077,"line":{"s":155,"e":156,"code":["  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the","  root heading. `relevelFragment(text, N)` puts the source root at"]},"column":{"s":47,"e":16}},"dim":["","list.36","listItem.2","paragraph.0","text.8"],"code":") + a regex strip of the\n  root heading. "},{"id":"/root/children/36/children/2/children/0/children/9","type":"inlineCode","loc":{"start":5077,"end":5103,"line":{"s":156,"e":156,"code":["  root heading. `relevelFragment(text, N)` puts the source root at"]},"column":{"s":16,"e":42}},"dim":["","list.36","listItem.2","paragraph.0","inlineCode.9"],"code":"`relevelFragment(text, N)`"},{"id":"/root/children/36/children/2/children/0/children/10","type":"text","loc":{"start":5103,"end":5136,"line":{"s":156,"e":157,"code":["  root heading. `relevelFragment(text, N)` puts the source root at","  depth `N+1`, so passing `targetDepth - 1` puts the root at"]},"column":{"s":42,"e":8}},"dim":["","list.36","listItem.2","paragraph.0","text.10"],"code":" puts the source root at\n  depth "},{"id":"/root/children/36/children/2/children/0/children/11","type":"inlineCode","loc":{"start":5136,"end":5141,"line":{"s":157,"e":157,"code":["  depth `N+1`, so passing `targetDepth - 1` puts the root at"]},"column":{"s":8,"e":13}},"dim":["","list.36","listItem.2","paragraph.0","inlineCode.11"],"code":"`N+1`"},{"id":"/root/children/36/children/2/children/0/children/12","type":"text","loc":{"start":5141,"end":5154,"line":{"s":157,"e":157,"code":["  depth `N+1`, so passing `targetDepth - 1` puts the root at"]},"column":{"s":13,"e":26}},"dim":["","list.36","listItem.2","paragraph.0","text.12"],"code":", so passing "},{"id":"/root/children/36/children/2/children/0/children/13","type":"inlineCode","loc":{"start":5154,"end":5171,"line":{"s":157,"e":157,"code":["  depth `N+1`, so passing `targetDepth - 1` puts the root at"]},"column":{"s":26,"e":43}},"dim":["","list.36","listItem.2","paragraph.0","inlineCode.13"],"code":"`targetDepth - 1`"},{"id":"/root/children/36/children/2/children/0/children/14","type":"text","loc":{"start":5171,"end":5189,"line":{"s":157,"e":158,"code":["  depth `N+1`, so passing `targetDepth - 1` puts the root at","  `targetDepth` — after the root-strip, the source's root+1 headings"]},"column":{"s":43,"e":0}},"dim":["","list.36","listItem.2","paragraph.0","text.14"],"code":" puts the root at\n"},{"id":"/root/children/36/children/2/children/0/children/15","type":"inlineCode","loc":{"start":5191,"end":5204,"line":{"s":158,"e":158,"code":["  `targetDepth` — after the root-strip, the source's root+1 headings"]},"column":{"s":2,"e":15}},"dim":["","list.36","listItem.2","paragraph.0","inlineCode.15"],"code":"`targetDepth`"},{"id":"/root/children/36/children/2/children/0/children/16","type":"text","loc":{"start":5204,"end":5298,"line":{"s":158,"e":159,"code":["  `targetDepth` — after the root-strip, the source's root+1 headings","  are what's left, correctly landing at `targetDepth+1`."]},"column":{"s":15,"e":40}},"dim":["","list.36","listItem.2","paragraph.0","text.16"],"code":" — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at "},{"id":"/root/children/36/children/2/children/0/children/17","type":"inlineCode","loc":{"start":5298,"end":5313,"line":{"s":159,"e":159,"code":["  are what's left, correctly landing at `targetDepth+1`."]},"column":{"s":40,"e":55}},"dim":["","list.36","listItem.2","paragraph.0","inlineCode.17"],"code":"`targetDepth+1`"},{"id":"/root/children/36/children/2/children/0/children/18","type":"text","loc":{"start":5313,"end":5314,"line":{"s":159,"e":159,"code":["  are what's left, correctly landing at `targetDepth+1`."]},"column":{"s":55,"e":56}},"dim":["","list.36","listItem.2","paragraph.0","text.18"],"code":"."},{"id":"/root/children/37","type":"heading","loc":{"start":5316,"end":5328,"line":{"s":161,"e":161,"code":["### Document"]},"column":{"s":0,"e":12}},"dim":["","heading.37"],"code":"### Document","symbName":"heading","symbRange":[5330,6269],"symbRangeL":[161,186],"outerCode":"\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.","outerHtml":"\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>"},{"id":"/root/children/37/children/0","type":"text","loc":{"start":5320,"end":5328,"line":{"s":161,"e":161,"code":["### Document"]},"column":{"s":4,"e":12}},"dim":["","heading.37","text.0"],"code":"Document"},{"id":"/root/children/38","type":"paragraph","loc":{"start":5330,"end":5455,"line":{"s":163,"e":164,"code":["A Document is both an **async iterable** (yields root-level Fragments) and","a **navigation hub** (find fragments by trail-id):"]},"column":{"s":0,"e":50}},"dim":["","paragraph.38"],"code":"A Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):"},{"id":"/root/children/38/children/0","type":"text","loc":{"start":5330,"end":5352,"line":{"s":163,"e":163,"code":["A Document is both an **async iterable** (yields root-level Fragments) and"]},"column":{"s":0,"e":22}},"dim":["","paragraph.38","text.0"],"code":"A Document is both an "},{"id":"/root/children/38/children/1","type":"strong","loc":{"start":5352,"end":5370,"line":{"s":163,"e":163,"code":["A Document is both an **async iterable** (yields root-level Fragments) and"]},"column":{"s":22,"e":40}},"dim":["","paragraph.38","strong.1"],"code":"**async iterable**"},{"id":"/root/children/38/children/1/children/0","type":"text","loc":{"start":5354,"end":5368,"line":{"s":163,"e":163,"code":["A Document is both an **async iterable** (yields root-level Fragments) and"]},"column":{"s":24,"e":38}},"dim":["","paragraph.38","strong.1","text.0"],"code":"async iterable"},{"id":"/root/children/38/children/2","type":"text","loc":{"start":5370,"end":5407,"line":{"s":163,"e":164,"code":["A Document is both an **async iterable** (yields root-level Fragments) and","a **navigation hub** (find fragments by trail-id):"]},"column":{"s":40,"e":2}},"dim":["","paragraph.38","text.2"],"code":" (yields root-level Fragments) and\na "},{"id":"/root/children/38/children/3","type":"strong","loc":{"start":5407,"end":5425,"line":{"s":164,"e":164,"code":["a **navigation hub** (find fragments by trail-id):"]},"column":{"s":2,"e":20}},"dim":["","paragraph.38","strong.3"],"code":"**navigation hub**"},{"id":"/root/children/38/children/3/children/0","type":"text","loc":{"start":5409,"end":5423,"line":{"s":164,"e":164,"code":["a **navigation hub** (find fragments by trail-id):"]},"column":{"s":4,"e":18}},"dim":["","paragraph.38","strong.3","text.0"],"code":"navigation hub"},{"id":"/root/children/38/children/4","type":"text","loc":{"start":5425,"end":5455,"line":{"s":164,"e":164,"code":["a **navigation hub** (find fragments by trail-id):"]},"column":{"s":20,"e":50}},"dim":["","paragraph.38","text.4"],"code":" (find fragments by trail-id):"},{"id":"/root/children/39","type":"code","loc":{"start":5458,"end":5664,"line":{"s":167,"e":172,"code":["```","doc[Symbol.asyncIterator]() → AsyncIterable<Fragment>","doc.find(trail)              → Fragment | undefined","doc.children(trail)          → AsyncIterable<Fragment>","doc.preamble                 → string","```"]},"column":{"s":0,"e":3}},"dim":["","code.39"],"code":"```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```","symbName":"code","symbRange":[5666,6292],"symbRangeL":[null,188],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>"},{"id":"/root/children/40","type":"list","loc":{"start":5666,"end":6117,"line":{"s":174,"e":181,"code":["- `preamble` — any text in the source that appears before the first heading.","  Empty string if there is none.","- `find(trail)` — walks lazily along the matching prefix only.","  At each level it compares the next trail segment against child sanitized","  names and expands _only_ the matching child, abandoning the rest.","  Cost is O(path length) expansions, not O(document).","  Returns `undefined` if no match.","- `children(trail)` — `find(trail)?.expand()`."]},"column":{"s":0,"e":46}},"dim":["","list.40"],"code":"- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.","symbName":"list","symbRange":[6119,7894],"symbRangeL":[174,245],"outerCode":"  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:","outerHtml":"<p>  Empty string if there is none.</p><ul><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>"},{"id":"/root/children/40/children/0","type":"listItem","loc":{"start":5666,"end":5775,"line":{"s":174,"e":175,"code":["- `preamble` — any text in the source that appears before the first heading.","  Empty string if there is none."]},"column":{"s":0,"e":32}},"dim":["","list.40","listItem.0"],"code":"- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none."},{"id":"/root/children/40/children/0/children/0","type":"paragraph","loc":{"start":5668,"end":5775,"line":{"s":174,"e":175,"code":["- `preamble` — any text in the source that appears before the first heading.","  Empty string if there is none."]},"column":{"s":2,"e":32}},"dim":["","list.40","listItem.0","paragraph.0"],"code":"`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none."},{"id":"/root/children/40/children/0/children/0/children/0","type":"inlineCode","loc":{"start":5668,"end":5678,"line":{"s":174,"e":174,"code":["- `preamble` — any text in the source that appears before the first heading."]},"column":{"s":2,"e":12}},"dim":["","list.40","listItem.0","paragraph.0","inlineCode.0"],"code":"`preamble`"},{"id":"/root/children/40/children/0/children/0/children/1","type":"text","loc":{"start":5678,"end":5775,"line":{"s":174,"e":175,"code":["- `preamble` — any text in the source that appears before the first heading.","  Empty string if there is none."]},"column":{"s":12,"e":32}},"dim":["","list.40","listItem.0","paragraph.0","text.1"],"code":" — any text in the source that appears before the first heading.\n  Empty string if there is none."},{"id":"/root/children/40/children/1","type":"listItem","loc":{"start":5776,"end":6070,"line":{"s":176,"e":180,"code":["- `find(trail)` — walks lazily along the matching prefix only.","  At each level it compares the next trail segment against child sanitized","  names and expands _only_ the matching child, abandoning the rest.","  Cost is O(path length) expansions, not O(document).","  Returns `undefined` if no match."]},"column":{"s":0,"e":34}},"dim":["","list.40","listItem.1"],"code":"- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match."},{"id":"/root/children/40/children/1/children/0","type":"paragraph","loc":{"start":5778,"end":6070,"line":{"s":176,"e":180,"code":["- `find(trail)` — walks lazily along the matching prefix only.","  At each level it compares the next trail segment against child sanitized","  names and expands _only_ the matching child, abandoning the rest.","  Cost is O(path length) expansions, not O(document).","  Returns `undefined` if no match."]},"column":{"s":2,"e":34}},"dim":["","list.40","listItem.1","paragraph.0"],"code":"`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match."},{"id":"/root/children/40/children/1/children/0/children/0","type":"inlineCode","loc":{"start":5778,"end":5791,"line":{"s":176,"e":176,"code":["- `find(trail)` — walks lazily along the matching prefix only."]},"column":{"s":2,"e":15}},"dim":["","list.40","listItem.1","paragraph.0","inlineCode.0"],"code":"`find(trail)`"},{"id":"/root/children/40/children/1/children/0/children/1","type":"text","loc":{"start":5791,"end":5934,"line":{"s":176,"e":178,"code":["- `find(trail)` — walks lazily along the matching prefix only.","  At each level it compares the next trail segment against child sanitized","  names and expands _only_ the matching child, abandoning the rest."]},"column":{"s":15,"e":20}},"dim":["","list.40","listItem.1","paragraph.0","text.1"],"code":" — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands "},{"id":"/root/children/40/children/1/children/0/children/2","type":"emphasis","loc":{"start":5934,"end":5940,"line":{"s":178,"e":178,"code":["  names and expands _only_ the matching child, abandoning the rest."]},"column":{"s":20,"e":26}},"dim":["","list.40","listItem.1","paragraph.0","emphasis.2"],"code":"_only_"},{"id":"/root/children/40/children/1/children/0/children/2/children/0","type":"text","loc":{"start":5935,"end":5939,"line":{"s":178,"e":178,"code":["  names and expands _only_ the matching child, abandoning the rest."]},"column":{"s":21,"e":25}},"dim":["","list.40","listItem.1","paragraph.0","emphasis.2","text.0"],"code":"only"},{"id":"/root/children/40/children/1/children/0/children/3","type":"text","loc":{"start":5940,"end":6046,"line":{"s":178,"e":180,"code":["  names and expands _only_ the matching child, abandoning the rest.","  Cost is O(path length) expansions, not O(document).","  Returns `undefined` if no match."]},"column":{"s":26,"e":10}},"dim":["","list.40","listItem.1","paragraph.0","text.3"],"code":" the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns "},{"id":"/root/children/40/children/1/children/0/children/4","type":"inlineCode","loc":{"start":6046,"end":6057,"line":{"s":180,"e":180,"code":["  Returns `undefined` if no match."]},"column":{"s":10,"e":21}},"dim":["","list.40","listItem.1","paragraph.0","inlineCode.4"],"code":"`undefined`"},{"id":"/root/children/40/children/1/children/0/children/5","type":"text","loc":{"start":6057,"end":6070,"line":{"s":180,"e":180,"code":["  Returns `undefined` if no match."]},"column":{"s":21,"e":34}},"dim":["","list.40","listItem.1","paragraph.0","text.5"],"code":" if no match."},{"id":"/root/children/40/children/2","type":"listItem","loc":{"start":6071,"end":6117,"line":{"s":181,"e":181,"code":["- `children(trail)` — `find(trail)?.expand()`."]},"column":{"s":0,"e":46}},"dim":["","list.40","listItem.2"],"code":"- `children(trail)` — `find(trail)?.expand()`."},{"id":"/root/children/40/children/2/children/0","type":"paragraph","loc":{"start":6073,"end":6117,"line":{"s":181,"e":181,"code":["- `children(trail)` — `find(trail)?.expand()`."]},"column":{"s":2,"e":46}},"dim":["","list.40","listItem.2","paragraph.0"],"code":"`children(trail)` — `find(trail)?.expand()`."},{"id":"/root/children/40/children/2/children/0/children/0","type":"inlineCode","loc":{"start":6073,"end":6090,"line":{"s":181,"e":181,"code":["- `children(trail)` — `find(trail)?.expand()`."]},"column":{"s":2,"e":19}},"dim":["","list.40","listItem.2","paragraph.0","inlineCode.0"],"code":"`children(trail)`"},{"id":"/root/children/40/children/2/children/0/children/1","type":"text","loc":{"start":6090,"end":6093,"line":{"s":181,"e":181,"code":["- `children(trail)` — `find(trail)?.expand()`."]},"column":{"s":19,"e":22}},"dim":["","list.40","listItem.2","paragraph.0","text.1"],"code":" — "},{"id":"/root/children/40/children/2/children/0/children/2","type":"inlineCode","loc":{"start":6093,"end":6116,"line":{"s":181,"e":181,"code":["- `children(trail)` — `find(trail)?.expand()`."]},"column":{"s":22,"e":45}},"dim":["","list.40","listItem.2","paragraph.0","inlineCode.2"],"code":"`find(trail)?.expand()`"},{"id":"/root/children/40/children/2/children/0/children/3","type":"text","loc":{"start":6116,"end":6117,"line":{"s":181,"e":181,"code":["- `children(trail)` — `find(trail)?.expand()`."]},"column":{"s":45,"e":46}},"dim":["","list.40","listItem.2","paragraph.0","text.3"],"code":"."},{"id":"/root/children/41","type":"paragraph","loc":{"start":6119,"end":6269,"line":{"s":183,"e":185,"code":["A Document is **stateless and re-iterable** — each call to","the runner produces a fresh Document, and each iteration re-derives from","the compiled tree."]},"column":{"s":0,"e":18}},"dim":["","paragraph.41"],"code":"A Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree."},{"id":"/root/children/41/children/0","type":"text","loc":{"start":6119,"end":6133,"line":{"s":183,"e":183,"code":["A Document is **stateless and re-iterable** — each call to"]},"column":{"s":0,"e":14}},"dim":["","paragraph.41","text.0"],"code":"A Document is "},{"id":"/root/children/41/children/1","type":"strong","loc":{"start":6133,"end":6162,"line":{"s":183,"e":183,"code":["A Document is **stateless and re-iterable** — each call to"]},"column":{"s":14,"e":43}},"dim":["","paragraph.41","strong.1"],"code":"**stateless and re-iterable**"},{"id":"/root/children/41/children/1/children/0","type":"text","loc":{"start":6135,"end":6160,"line":{"s":183,"e":183,"code":["A Document is **stateless and re-iterable** — each call to"]},"column":{"s":16,"e":41}},"dim":["","paragraph.41","strong.1","text.0"],"code":"stateless and re-iterable"},{"id":"/root/children/41/children/2","type":"text","loc":{"start":6162,"end":6269,"line":{"s":183,"e":185,"code":["A Document is **stateless and re-iterable** — each call to","the runner produces a fresh Document, and each iteration re-derives from","the compiled tree."]},"column":{"s":43,"e":18}},"dim":["","paragraph.41","text.2"],"code":" — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree."},{"id":"/root/children/42","type":"heading","loc":{"start":6271,"end":6292,"line":{"s":187,"e":187,"code":["### Usage — Iteration"]},"column":{"s":0,"e":21}},"dim":["","heading.42"],"code":"### Usage — Iteration","symbName":"heading","symbRange":[6294,6655],"symbRangeL":[187,204],"outerCode":"\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```","outerHtml":"\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>"},{"id":"/root/children/42/children/0","type":"text","loc":{"start":6275,"end":6292,"line":{"s":187,"e":187,"code":["### Usage — Iteration"]},"column":{"s":4,"e":21}},"dim":["","heading.42","text.0"],"code":"Usage — Iteration"},{"id":"/root/children/43","type":"code","loc":{"start":6294,"end":6655,"line":{"s":189,"e":203,"code":["```js","const doc = runner({ user });","","for await (const section of doc) {","  // section.heading → \"# Chapter 1\"","  // section.body → \"Some text...\"","  // section.toString() → \"# Chapter 1\\n\\nSome text...\"","","  for await (const child of section.expand()) {","    // child.heading → \"## Section 1.1\"","    // child.headingLevel → 2","    // child.body → \"Details...\"","  }","}","```"]},"column":{"s":0,"e":3}},"dim":["","code.43"],"code":"```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```","symbName":"code","symbRange":[6657,6685],"symbRangeL":[null,206],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>"},{"id":"/root/children/44","type":"heading","loc":{"start":6657,"end":6685,"line":{"s":205,"e":205,"code":["### Usage — Trail navigation"]},"column":{"s":0,"e":28}},"dim":["","heading.44"],"code":"### Usage — Trail navigation","symbName":"heading","symbRange":[6687,7183],"symbRangeL":[205,229],"outerCode":"\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```","outerHtml":"\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>"},{"id":"/root/children/44/children/0","type":"text","loc":{"start":6661,"end":6685,"line":{"s":205,"e":205,"code":["### Usage — Trail navigation"]},"column":{"s":4,"e":28}},"dim":["","heading.44","text.0"],"code":"Usage — Trail navigation"},{"id":"/root/children/45","type":"code","loc":{"start":6687,"end":7183,"line":{"s":207,"e":228,"code":["```js","const doc = runner(","  { user },","  {","    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),","  },",");","","// Find a heading by trail-id","const section = doc.find(\"getting-started/installation\");","for await (const step of section.expand()) {","  // immediate children of ## Installation","}","","// Or shortcut: get children directly","for await (const step of doc.children(\"getting-started/installation\")) {","  // same result","}","","// Preamble text before the first heading","console.log(doc.preamble);","```"]},"column":{"s":0,"e":3}},"dim":["","code.45"],"code":"```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```","symbName":"code","symbRange":[7185,8793],"symbRangeL":[null,266],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n"},{"id":"/root/children/46","type":"heading","loc":{"start":7185,"end":7197,"line":{"s":230,"e":230,"code":["### Trail-id"]},"column":{"s":0,"e":12}},"dim":["","heading.46"],"code":"### Trail-id","symbName":"heading","symbRange":[7199,8630],"symbRangeL":[230,260],"outerCode":"\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.","outerHtml":"\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>"},{"id":"/root/children/46/children/0","type":"text","loc":{"start":7189,"end":7197,"line":{"s":230,"e":230,"code":["### Trail-id"]},"column":{"s":4,"e":12}},"dim":["","heading.46","text.0"],"code":"Trail-id"},{"id":"/root/children/47","type":"paragraph","loc":{"start":7199,"end":7326,"line":{"s":232,"e":233,"code":["A **trail-id** is a `/`-separated path of sanitized heading names that","uniquely identifies a heading in the document hierarchy:"]},"column":{"s":0,"e":56}},"dim":["","paragraph.47"],"code":"A **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:"},{"id":"/root/children/47/children/0","type":"text","loc":{"start":7199,"end":7201,"line":{"s":232,"e":232,"code":["A **trail-id** is a `/`-separated path of sanitized heading names that"]},"column":{"s":0,"e":2}},"dim":["","paragraph.47","text.0"],"code":"A "},{"id":"/root/children/47/children/1","type":"strong","loc":{"start":7201,"end":7213,"line":{"s":232,"e":232,"code":["A **trail-id** is a `/`-separated path of sanitized heading names that"]},"column":{"s":2,"e":14}},"dim":["","paragraph.47","strong.1"],"code":"**trail-id**"},{"id":"/root/children/47/children/1/children/0","type":"text","loc":{"start":7203,"end":7211,"line":{"s":232,"e":232,"code":["A **trail-id** is a `/`-separated path of sanitized heading names that"]},"column":{"s":4,"e":12}},"dim":["","paragraph.47","strong.1","text.0"],"code":"trail-id"},{"id":"/root/children/47/children/2","type":"text","loc":{"start":7213,"end":7219,"line":{"s":232,"e":232,"code":["A **trail-id** is a `/`-separated path of sanitized heading names that"]},"column":{"s":14,"e":20}},"dim":["","paragraph.47","text.2"],"code":" is a "},{"id":"/root/children/47/children/3","type":"inlineCode","loc":{"start":7219,"end":7222,"line":{"s":232,"e":232,"code":["A **trail-id** is a `/`-separated path of sanitized heading names that"]},"column":{"s":20,"e":23}},"dim":["","paragraph.47","inlineCode.3"],"code":"`/`"},{"id":"/root/children/47/children/4","type":"text","loc":{"start":7222,"end":7326,"line":{"s":232,"e":233,"code":["A **trail-id** is a `/`-separated path of sanitized heading names that","uniquely identifies a heading in the document hierarchy:"]},"column":{"s":23,"e":56}},"dim":["","paragraph.47","text.4"],"code":"-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:"},{"id":"/root/children/48","type":"paragraph","loc":{"start":7328,"end":7782,"line":{"s":235,"e":241,"code":["| Heading             | Trail                                  |","| ------------------- | -------------------------------------- |","| `# Getting Started` | `\"getting-started\"`                    |","| `## Installation`   | `\"getting-started/installation\"`       |","| `### Linux`         | `\"getting-started/installation/linux\"` |","| `### macOS`         | `\"getting-started/installation/macos\"` |","| `## Usage`          | `\"getting-started/usage\"`              |"]},"column":{"s":0,"e":64}},"dim":["","paragraph.48"],"code":"| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |"},{"id":"/root/children/48/children/0","type":"text","loc":{"start":7328,"end":7460,"line":{"s":235,"e":237,"code":["| Heading             | Trail                                  |","| ------------------- | -------------------------------------- |","| `# Getting Started` | `\"getting-started\"`                    |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.48","text.0"],"code":"| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| "},{"id":"/root/children/48/children/1","type":"inlineCode","loc":{"start":7460,"end":7479,"line":{"s":237,"e":237,"code":["| `# Getting Started` | `\"getting-started\"`                    |"]},"column":{"s":2,"e":21}},"dim":["","paragraph.48","inlineCode.1"],"code":"`# Getting Started`"},{"id":"/root/children/48/children/2","type":"text","loc":{"start":7479,"end":7482,"line":{"s":237,"e":237,"code":["| `# Getting Started` | `\"getting-started\"`                    |"]},"column":{"s":21,"e":24}},"dim":["","paragraph.48","text.2"],"code":" | "},{"id":"/root/children/48/children/3","type":"inlineCode","loc":{"start":7482,"end":7501,"line":{"s":237,"e":237,"code":["| `# Getting Started` | `\"getting-started\"`                    |"]},"column":{"s":24,"e":43}},"dim":["","paragraph.48","inlineCode.3"],"code":"`\"getting-started\"`"},{"id":"/root/children/48/children/4","type":"text","loc":{"start":7501,"end":7525,"line":{"s":237,"e":238,"code":["| `# Getting Started` | `\"getting-started\"`                    |","| `## Installation`   | `\"getting-started/installation\"`       |"]},"column":{"s":43,"e":2}},"dim":["","paragraph.48","text.4"],"code":"                    |\n| "},{"id":"/root/children/48/children/5","type":"inlineCode","loc":{"start":7525,"end":7542,"line":{"s":238,"e":238,"code":["| `## Installation`   | `\"getting-started/installation\"`       |"]},"column":{"s":2,"e":19}},"dim":["","paragraph.48","inlineCode.5"],"code":"`## Installation`"},{"id":"/root/children/48/children/6","type":"text","loc":{"start":7542,"end":7547,"line":{"s":238,"e":238,"code":["| `## Installation`   | `\"getting-started/installation\"`       |"]},"column":{"s":19,"e":24}},"dim":["","paragraph.48","text.6"],"code":"   | "},{"id":"/root/children/48/children/7","type":"inlineCode","loc":{"start":7547,"end":7579,"line":{"s":238,"e":238,"code":["| `## Installation`   | `\"getting-started/installation\"`       |"]},"column":{"s":24,"e":56}},"dim":["","paragraph.48","inlineCode.7"],"code":"`\"getting-started/installation\"`"},{"id":"/root/children/48/children/8","type":"text","loc":{"start":7579,"end":7590,"line":{"s":238,"e":239,"code":["| `## Installation`   | `\"getting-started/installation\"`       |","| `### Linux`         | `\"getting-started/installation/linux\"` |"]},"column":{"s":56,"e":2}},"dim":["","paragraph.48","text.8"],"code":"       |\n| "},{"id":"/root/children/48/children/9","type":"inlineCode","loc":{"start":7590,"end":7601,"line":{"s":239,"e":239,"code":["| `### Linux`         | `\"getting-started/installation/linux\"` |"]},"column":{"s":2,"e":13}},"dim":["","paragraph.48","inlineCode.9"],"code":"`### Linux`"},{"id":"/root/children/48/children/10","type":"text","loc":{"start":7601,"end":7612,"line":{"s":239,"e":239,"code":["| `### Linux`         | `\"getting-started/installation/linux\"` |"]},"column":{"s":13,"e":24}},"dim":["","paragraph.48","text.10"],"code":"         | "},{"id":"/root/children/48/children/11","type":"inlineCode","loc":{"start":7612,"end":7650,"line":{"s":239,"e":239,"code":["| `### Linux`         | `\"getting-started/installation/linux\"` |"]},"column":{"s":24,"e":62}},"dim":["","paragraph.48","inlineCode.11"],"code":"`\"getting-started/installation/linux\"`"},{"id":"/root/children/48/children/12","type":"text","loc":{"start":7650,"end":7655,"line":{"s":239,"e":240,"code":["| `### Linux`         | `\"getting-started/installation/linux\"` |","| `### macOS`         | `\"getting-started/installation/macos\"` |"]},"column":{"s":62,"e":2}},"dim":["","paragraph.48","text.12"],"code":" |\n| "},{"id":"/root/children/48/children/13","type":"inlineCode","loc":{"start":7655,"end":7666,"line":{"s":240,"e":240,"code":["| `### macOS`         | `\"getting-started/installation/macos\"` |"]},"column":{"s":2,"e":13}},"dim":["","paragraph.48","inlineCode.13"],"code":"`### macOS`"},{"id":"/root/children/48/children/14","type":"text","loc":{"start":7666,"end":7677,"line":{"s":240,"e":240,"code":["| `### macOS`         | `\"getting-started/installation/macos\"` |"]},"column":{"s":13,"e":24}},"dim":["","paragraph.48","text.14"],"code":"         | "},{"id":"/root/children/48/children/15","type":"inlineCode","loc":{"start":7677,"end":7715,"line":{"s":240,"e":240,"code":["| `### macOS`         | `\"getting-started/installation/macos\"` |"]},"column":{"s":24,"e":62}},"dim":["","paragraph.48","inlineCode.15"],"code":"`\"getting-started/installation/macos\"`"},{"id":"/root/children/48/children/16","type":"text","loc":{"start":7715,"end":7720,"line":{"s":240,"e":241,"code":["| `### macOS`         | `\"getting-started/installation/macos\"` |","| `## Usage`          | `\"getting-started/usage\"`              |"]},"column":{"s":62,"e":2}},"dim":["","paragraph.48","text.16"],"code":" |\n| "},{"id":"/root/children/48/children/17","type":"inlineCode","loc":{"start":7720,"end":7730,"line":{"s":241,"e":241,"code":["| `## Usage`          | `\"getting-started/usage\"`              |"]},"column":{"s":2,"e":12}},"dim":["","paragraph.48","inlineCode.17"],"code":"`## Usage`"},{"id":"/root/children/48/children/18","type":"text","loc":{"start":7730,"end":7742,"line":{"s":241,"e":241,"code":["| `## Usage`          | `\"getting-started/usage\"`              |"]},"column":{"s":12,"e":24}},"dim":["","paragraph.48","text.18"],"code":"          | "},{"id":"/root/children/48/children/19","type":"inlineCode","loc":{"start":7742,"end":7767,"line":{"s":241,"e":241,"code":["| `## Usage`          | `\"getting-started/usage\"`              |"]},"column":{"s":24,"e":49}},"dim":["","paragraph.48","inlineCode.19"],"code":"`\"getting-started/usage\"`"},{"id":"/root/children/48/children/20","type":"text","loc":{"start":7767,"end":7782,"line":{"s":241,"e":241,"code":["| `## Usage`          | `\"getting-started/usage\"`              |"]},"column":{"s":49,"e":64}},"dim":["","paragraph.48","text.20"],"code":"              |"},{"id":"/root/children/49","type":"paragraph","loc":{"start":7784,"end":7894,"line":{"s":243,"e":244,"code":["The trail is constructed with **the same stack algorithm** used by","`getHeadingTrail` in the existing codebase:"]},"column":{"s":0,"e":43}},"dim":["","paragraph.49"],"code":"The trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:"},{"id":"/root/children/49/children/0","type":"text","loc":{"start":7784,"end":7814,"line":{"s":243,"e":243,"code":["The trail is constructed with **the same stack algorithm** used by"]},"column":{"s":0,"e":30}},"dim":["","paragraph.49","text.0"],"code":"The trail is constructed with "},{"id":"/root/children/49/children/1","type":"strong","loc":{"start":7814,"end":7842,"line":{"s":243,"e":243,"code":["The trail is constructed with **the same stack algorithm** used by"]},"column":{"s":30,"e":58}},"dim":["","paragraph.49","strong.1"],"code":"**the same stack algorithm**"},{"id":"/root/children/49/children/1/children/0","type":"text","loc":{"start":7816,"end":7840,"line":{"s":243,"e":243,"code":["The trail is constructed with **the same stack algorithm** used by"]},"column":{"s":32,"e":56}},"dim":["","paragraph.49","strong.1","text.0"],"code":"the same stack algorithm"},{"id":"/root/children/49/children/2","type":"text","loc":{"start":7842,"end":7851,"line":{"s":243,"e":244,"code":["The trail is constructed with **the same stack algorithm** used by","`getHeadingTrail` in the existing codebase:"]},"column":{"s":58,"e":0}},"dim":["","paragraph.49","text.2"],"code":" used by\n"},{"id":"/root/children/49/children/3","type":"inlineCode","loc":{"start":7851,"end":7868,"line":{"s":244,"e":244,"code":["`getHeadingTrail` in the existing codebase:"]},"column":{"s":0,"e":17}},"dim":["","paragraph.49","inlineCode.3"],"code":"`getHeadingTrail`"},{"id":"/root/children/49/children/4","type":"text","loc":{"start":7868,"end":7894,"line":{"s":244,"e":244,"code":["`getHeadingTrail` in the existing codebase:"]},"column":{"s":17,"e":43}},"dim":["","paragraph.49","text.4"],"code":" in the existing codebase:"},{"id":"/root/children/50","type":"list","loc":{"start":7896,"end":8176,"line":{"s":246,"e":250,"code":["1. Walk all heading nodes depth-first (in document order)","1. Maintain a stack of `{ level, sanitized }` entries","1. When a heading at level N is encountered, pop all stack entries where","   `level >= N`, then push this heading","1. The trail is `stack.map(e => e.sanitized).join(\"/\")`"]},"column":{"s":0,"e":55}},"dim":["","list.50"],"code":"1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`","symbName":"list","symbRange":[8178,14466],"symbRangeL":[246,478],"outerCode":"1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```","outerHtml":"<ol><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>"},{"id":"/root/children/50/children/0","type":"listItem","loc":{"start":7896,"end":7953,"line":{"s":246,"e":246,"code":["1. Walk all heading nodes depth-first (in document order)"]},"column":{"s":0,"e":57}},"dim":["","list.50","listItem.0"],"code":"1. Walk all heading nodes depth-first (in document order)"},{"id":"/root/children/50/children/0/children/0","type":"paragraph","loc":{"start":7899,"end":7953,"line":{"s":246,"e":246,"code":["1. Walk all heading nodes depth-first (in document order)"]},"column":{"s":3,"e":57}},"dim":["","list.50","listItem.0","paragraph.0"],"code":"Walk all heading nodes depth-first (in document order)"},{"id":"/root/children/50/children/0/children/0/children/0","type":"text","loc":{"start":7899,"end":7953,"line":{"s":246,"e":246,"code":["1. Walk all heading nodes depth-first (in document order)"]},"column":{"s":3,"e":57}},"dim":["","list.50","listItem.0","paragraph.0","text.0"],"code":"Walk all heading nodes depth-first (in document order)"},{"id":"/root/children/50/children/1","type":"listItem","loc":{"start":7954,"end":8007,"line":{"s":247,"e":247,"code":["1. Maintain a stack of `{ level, sanitized }` entries"]},"column":{"s":0,"e":53}},"dim":["","list.50","listItem.1"],"code":"1. Maintain a stack of `{ level, sanitized }` entries"},{"id":"/root/children/50/children/1/children/0","type":"paragraph","loc":{"start":7957,"end":8007,"line":{"s":247,"e":247,"code":["1. Maintain a stack of `{ level, sanitized }` entries"]},"column":{"s":3,"e":53}},"dim":["","list.50","listItem.1","paragraph.0"],"code":"Maintain a stack of `{ level, sanitized }` entries"},{"id":"/root/children/50/children/1/children/0/children/0","type":"text","loc":{"start":7957,"end":7977,"line":{"s":247,"e":247,"code":["1. Maintain a stack of `{ level, sanitized }` entries"]},"column":{"s":3,"e":23}},"dim":["","list.50","listItem.1","paragraph.0","text.0"],"code":"Maintain a stack of "},{"id":"/root/children/50/children/1/children/0/children/1","type":"inlineCode","loc":{"start":7977,"end":7999,"line":{"s":247,"e":247,"code":["1. Maintain a stack of `{ level, sanitized }` entries"]},"column":{"s":23,"e":45}},"dim":["","list.50","listItem.1","paragraph.0","inlineCode.1"],"code":"`{ level, sanitized }`"},{"id":"/root/children/50/children/1/children/0/children/2","type":"text","loc":{"start":7999,"end":8007,"line":{"s":247,"e":247,"code":["1. Maintain a stack of `{ level, sanitized }` entries"]},"column":{"s":45,"e":53}},"dim":["","list.50","listItem.1","paragraph.0","text.2"],"code":" entries"},{"id":"/root/children/50/children/2","type":"listItem","loc":{"start":8008,"end":8120,"line":{"s":248,"e":249,"code":["1. When a heading at level N is encountered, pop all stack entries where","   `level >= N`, then push this heading"]},"column":{"s":0,"e":39}},"dim":["","list.50","listItem.2"],"code":"1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading"},{"id":"/root/children/50/children/2/children/0","type":"paragraph","loc":{"start":8011,"end":8120,"line":{"s":248,"e":249,"code":["1. When a heading at level N is encountered, pop all stack entries where","   `level >= N`, then push this heading"]},"column":{"s":3,"e":39}},"dim":["","list.50","listItem.2","paragraph.0"],"code":"When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading"},{"id":"/root/children/50/children/2/children/0/children/0","type":"text","loc":{"start":8011,"end":8081,"line":{"s":248,"e":249,"code":["1. When a heading at level N is encountered, pop all stack entries where","   `level >= N`, then push this heading"]},"column":{"s":3,"e":0}},"dim":["","list.50","listItem.2","paragraph.0","text.0"],"code":"When a heading at level N is encountered, pop all stack entries where\n"},{"id":"/root/children/50/children/2/children/0/children/1","type":"inlineCode","loc":{"start":8084,"end":8096,"line":{"s":249,"e":249,"code":["   `level >= N`, then push this heading"]},"column":{"s":3,"e":15}},"dim":["","list.50","listItem.2","paragraph.0","inlineCode.1"],"code":"`level >= N`"},{"id":"/root/children/50/children/2/children/0/children/2","type":"text","loc":{"start":8096,"end":8120,"line":{"s":249,"e":249,"code":["   `level >= N`, then push this heading"]},"column":{"s":15,"e":39}},"dim":["","list.50","listItem.2","paragraph.0","text.2"],"code":", then push this heading"},{"id":"/root/children/50/children/3","type":"listItem","loc":{"start":8121,"end":8176,"line":{"s":250,"e":250,"code":["1. The trail is `stack.map(e => e.sanitized).join(\"/\")`"]},"column":{"s":0,"e":55}},"dim":["","list.50","listItem.3"],"code":"1. The trail is `stack.map(e => e.sanitized).join(\"/\")`"},{"id":"/root/children/50/children/3/children/0","type":"paragraph","loc":{"start":8124,"end":8176,"line":{"s":250,"e":250,"code":["1. The trail is `stack.map(e => e.sanitized).join(\"/\")`"]},"column":{"s":3,"e":55}},"dim":["","list.50","listItem.3","paragraph.0"],"code":"The trail is `stack.map(e => e.sanitized).join(\"/\")`"},{"id":"/root/children/50/children/3/children/0/children/0","type":"text","loc":{"start":8124,"end":8137,"line":{"s":250,"e":250,"code":["1. The trail is `stack.map(e => e.sanitized).join(\"/\")`"]},"column":{"s":3,"e":16}},"dim":["","list.50","listItem.3","paragraph.0","text.0"],"code":"The trail is "},{"id":"/root/children/50/children/3/children/0/children/1","type":"inlineCode","loc":{"start":8137,"end":8176,"line":{"s":250,"e":250,"code":["1. The trail is `stack.map(e => e.sanitized).join(\"/\")`"]},"column":{"s":16,"e":55}},"dim":["","list.50","listItem.3","paragraph.0","inlineCode.1"],"code":"`stack.map(e => e.sanitized).join(\"/\")`"},{"id":"/root/children/51","type":"paragraph","loc":{"start":8178,"end":8433,"line":{"s":252,"e":255,"code":["**Extructions** (`# ${label}`) are skipped by","the trail algorithm — they produce no output and don't contribute to the stack.","A `## Details` after an extruction `## ${sidebar}`","at the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`."]},"column":{"s":0,"e":78}},"dim":["","paragraph.51"],"code":"**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`."},{"id":"/root/children/51/children/0","type":"strong","loc":{"start":8178,"end":8193,"line":{"s":252,"e":252,"code":["**Extructions** (`# ${label}`) are skipped by"]},"column":{"s":0,"e":15}},"dim":["","paragraph.51","strong.0"],"code":"**Extructions**"},{"id":"/root/children/51/children/0/children/0","type":"text","loc":{"start":8180,"end":8191,"line":{"s":252,"e":252,"code":["**Extructions** (`# ${label}`) are skipped by"]},"column":{"s":2,"e":13}},"dim":["","paragraph.51","strong.0","text.0"],"code":"Extructions"},{"id":"/root/children/51/children/1","type":"text","loc":{"start":8193,"end":8195,"line":{"s":252,"e":252,"code":["**Extructions** (`# ${label}`) are skipped by"]},"column":{"s":15,"e":17}},"dim":["","paragraph.51","text.1"],"code":" ("},{"id":"/root/children/51/children/2","type":"inlineCode","loc":{"start":8195,"end":8207,"line":{"s":252,"e":252,"code":["**Extructions** (`# ${label}`) are skipped by"]},"column":{"s":17,"e":29}},"dim":["","paragraph.51","inlineCode.2"],"code":"`# ${label}`"},{"id":"/root/children/51/children/3","type":"text","loc":{"start":8207,"end":8306,"line":{"s":252,"e":254,"code":["**Extructions** (`# ${label}`) are skipped by","the trail algorithm — they produce no output and don't contribute to the stack.","A `## Details` after an extruction `## ${sidebar}`"]},"column":{"s":29,"e":2}},"dim":["","paragraph.51","text.3"],"code":") are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA "},{"id":"/root/children/51/children/4","type":"inlineCode","loc":{"start":8306,"end":8318,"line":{"s":254,"e":254,"code":["A `## Details` after an extruction `## ${sidebar}`"]},"column":{"s":2,"e":14}},"dim":["","paragraph.51","inlineCode.4"],"code":"`## Details`"},{"id":"/root/children/51/children/5","type":"text","loc":{"start":8318,"end":8339,"line":{"s":254,"e":254,"code":["A `## Details` after an extruction `## ${sidebar}`"]},"column":{"s":14,"e":35}},"dim":["","paragraph.51","text.5"],"code":" after an extruction "},{"id":"/root/children/51/children/6","type":"inlineCode","loc":{"start":8339,"end":8354,"line":{"s":254,"e":254,"code":["A `## Details` after an extruction `## ${sidebar}`"]},"column":{"s":35,"e":50}},"dim":["","paragraph.51","inlineCode.6"],"code":"`## ${sidebar}`"},{"id":"/root/children/51/children/7","type":"text","loc":{"start":8354,"end":8384,"line":{"s":254,"e":255,"code":["A `## Details` after an extruction `## ${sidebar}`","at the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`."]},"column":{"s":50,"e":29}},"dim":["","paragraph.51","text.7"],"code":"\nat the same level gets trail "},{"id":"/root/children/51/children/8","type":"inlineCode","loc":{"start":8384,"end":8401,"line":{"s":255,"e":255,"code":["at the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`."]},"column":{"s":29,"e":46}},"dim":["","paragraph.51","inlineCode.8"],"code":"`\"intro/details\"`"},{"id":"/root/children/51/children/9","type":"text","loc":{"start":8401,"end":8407,"line":{"s":255,"e":255,"code":["at the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`."]},"column":{"s":46,"e":52}},"dim":["","paragraph.51","text.9"],"code":", not "},{"id":"/root/children/51/children/10","type":"inlineCode","loc":{"start":8407,"end":8432,"line":{"s":255,"e":255,"code":["at the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`."]},"column":{"s":52,"e":77}},"dim":["","paragraph.51","inlineCode.10"],"code":"`\"intro/sidebar/details\"`"},{"id":"/root/children/51/children/11","type":"text","loc":{"start":8432,"end":8433,"line":{"s":255,"e":255,"code":["at the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`."]},"column":{"s":77,"e":78}},"dim":["","paragraph.51","text.11"],"code":"."},{"id":"/root/children/52","type":"paragraph","loc":{"start":8435,"end":8630,"line":{"s":257,"e":259,"code":["Traversal stops at the **first match** — `find()` and `children()`","return the section at the exact trail without pre-processing the entire","document. Fragments past the match are not materialized."]},"column":{"s":0,"e":56}},"dim":["","paragraph.52"],"code":"Traversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized."},{"id":"/root/children/52/children/0","type":"text","loc":{"start":8435,"end":8458,"line":{"s":257,"e":257,"code":["Traversal stops at the **first match** — `find()` and `children()`"]},"column":{"s":0,"e":23}},"dim":["","paragraph.52","text.0"],"code":"Traversal stops at the "},{"id":"/root/children/52/children/1","type":"strong","loc":{"start":8458,"end":8473,"line":{"s":257,"e":257,"code":["Traversal stops at the **first match** — `find()` and `children()`"]},"column":{"s":23,"e":38}},"dim":["","paragraph.52","strong.1"],"code":"**first match**"},{"id":"/root/children/52/children/1/children/0","type":"text","loc":{"start":8460,"end":8471,"line":{"s":257,"e":257,"code":["Traversal stops at the **first match** — `find()` and `children()`"]},"column":{"s":25,"e":36}},"dim":["","paragraph.52","strong.1","text.0"],"code":"first match"},{"id":"/root/children/52/children/2","type":"text","loc":{"start":8473,"end":8476,"line":{"s":257,"e":257,"code":["Traversal stops at the **first match** — `find()` and `children()`"]},"column":{"s":38,"e":41}},"dim":["","paragraph.52","text.2"],"code":" — "},{"id":"/root/children/52/children/3","type":"inlineCode","loc":{"start":8476,"end":8484,"line":{"s":257,"e":257,"code":["Traversal stops at the **first match** — `find()` and `children()`"]},"column":{"s":41,"e":49}},"dim":["","paragraph.52","inlineCode.3"],"code":"`find()`"},{"id":"/root/children/52/children/4","type":"text","loc":{"start":8484,"end":8489,"line":{"s":257,"e":257,"code":["Traversal stops at the **first match** — `find()` and `children()`"]},"column":{"s":49,"e":54}},"dim":["","paragraph.52","text.4"],"code":" and "},{"id":"/root/children/52/children/5","type":"inlineCode","loc":{"start":8489,"end":8501,"line":{"s":257,"e":257,"code":["Traversal stops at the **first match** — `find()` and `children()`"]},"column":{"s":54,"e":66}},"dim":["","paragraph.52","inlineCode.5"],"code":"`children()`"},{"id":"/root/children/52/children/6","type":"text","loc":{"start":8501,"end":8630,"line":{"s":257,"e":259,"code":["Traversal stops at the **first match** — `find()` and `children()`","return the section at the exact trail without pre-processing the entire","document. Fragments past the match are not materialized."]},"column":{"s":66,"e":56}},"dim":["","paragraph.52","text.6"],"code":"\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized."},{"id":"/root/children/53","type":"heading","loc":{"start":8632,"end":8679,"line":{"s":261,"e":261,"code":["### Usage — Extruction evaluation with adapters"]},"column":{"s":0,"e":47}},"dim":["","heading.53"],"code":"### Usage — Extruction evaluation with adapters","symbName":"heading","symbRange":[8681,10017],"symbRangeL":[261,319],"outerCode":"\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.","outerHtml":"\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>"},{"id":"/root/children/53/children/0","type":"text","loc":{"start":8636,"end":8679,"line":{"s":261,"e":261,"code":["### Usage — Extruction evaluation with adapters"]},"column":{"s":4,"e":47}},"dim":["","heading.53","text.0"],"code":"Usage — Extruction evaluation with adapters"},{"id":"/root/children/54","type":"paragraph","loc":{"start":8681,"end":8793,"line":{"s":263,"e":264,"code":["When `evalFn` is provided, extruction bodies run as JavaScript and can","produce output via the `insert` protocol:"]},"column":{"s":0,"e":41}},"dim":["","paragraph.54"],"code":"When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:"},{"id":"/root/children/54/children/0","type":"text","loc":{"start":8681,"end":8686,"line":{"s":263,"e":263,"code":["When `evalFn` is provided, extruction bodies run as JavaScript and can"]},"column":{"s":0,"e":5}},"dim":["","paragraph.54","text.0"],"code":"When "},{"id":"/root/children/54/children/1","type":"inlineCode","loc":{"start":8686,"end":8694,"line":{"s":263,"e":263,"code":["When `evalFn` is provided, extruction bodies run as JavaScript and can"]},"column":{"s":5,"e":13}},"dim":["","paragraph.54","inlineCode.1"],"code":"`evalFn`"},{"id":"/root/children/54/children/2","type":"text","loc":{"start":8694,"end":8775,"line":{"s":263,"e":264,"code":["When `evalFn` is provided, extruction bodies run as JavaScript and can","produce output via the `insert` protocol:"]},"column":{"s":13,"e":23}},"dim":["","paragraph.54","text.2"],"code":" is provided, extruction bodies run as JavaScript and can\nproduce output via the "},{"id":"/root/children/54/children/3","type":"inlineCode","loc":{"start":8775,"end":8783,"line":{"s":264,"e":264,"code":["produce output via the `insert` protocol:"]},"column":{"s":23,"e":31}},"dim":["","paragraph.54","inlineCode.3"],"code":"`insert`"},{"id":"/root/children/54/children/4","type":"text","loc":{"start":8783,"end":8793,"line":{"s":264,"e":264,"code":["produce output via the `insert` protocol:"]},"column":{"s":31,"e":41}},"dim":["","paragraph.54","text.4"],"code":" protocol:"},{"id":"/root/children/55","type":"code","loc":{"start":8796,"end":9786,"line":{"s":267,"e":314,"code":["```js","import { compile } from './mdt/mdt.js'","import { evalBody } from './mdt/eval-body.js'","import { remark } from 'remark'","","const md = `# ${greeting}","","\\`\\`\\`javascript","const name = _mdt_label","return insert(\\`Hello **\\${name}**\\`)","\\`\\`\\`","","# Results","","## ${search mdd}","","\\`\\`\\`javascript","const items = await search(\"mdd\")","return insert(items.map(i => i.uri).join(\"\\\\n\"))","\\`\\`\\`","","## Total","","\\`\\`\\`javascript","return insert(String(total))","\\`\\`\\`","`","","const search = async (q) => [","  { name: \"file1\", uri: \"#/paper/file1\" },","  { name: \"file2\", uri: \"#/paper/file2\" },","]","const total = 42","","const runner = compile(md, { remark })","const doc = runner({ search, total }, { evalFn: evalBody })","","for await (const section of doc) {","  console.log(section.toString())","  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"","  // \"Results\" → normal heading, expanded below","","  for await (const child of section.expand()) {","    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"","    // \"Total\" → \"42\"","  }","}","```"]},"column":{"s":0,"e":3}},"dim":["","code.55"],"code":"```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```","symbName":"code","symbRange":[9788,10155],"symbRangeL":[null,325],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n"},{"id":"/root/children/56","type":"paragraph","loc":{"start":9788,"end":10017,"line":{"s":316,"e":318,"code":["The extruction body `return insert(value)` yields one or more Fragment-like","objects directly into the output. Any `await`-able function in context is an","adapter — `search`, `total`, and `_mdt_label` all coexist as named bindings."]},"column":{"s":0,"e":76}},"dim":["","paragraph.56"],"code":"The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings."},{"id":"/root/children/56/children/0","type":"text","loc":{"start":9788,"end":9808,"line":{"s":316,"e":316,"code":["The extruction body `return insert(value)` yields one or more Fragment-like"]},"column":{"s":0,"e":20}},"dim":["","paragraph.56","text.0"],"code":"The extruction body "},{"id":"/root/children/56/children/1","type":"inlineCode","loc":{"start":9808,"end":9830,"line":{"s":316,"e":316,"code":["The extruction body `return insert(value)` yields one or more Fragment-like"]},"column":{"s":20,"e":42}},"dim":["","paragraph.56","inlineCode.1"],"code":"`return insert(value)`"},{"id":"/root/children/56/children/2","type":"text","loc":{"start":9830,"end":9902,"line":{"s":316,"e":317,"code":["The extruction body `return insert(value)` yields one or more Fragment-like","objects directly into the output. Any `await`-able function in context is an"]},"column":{"s":42,"e":38}},"dim":["","paragraph.56","text.2"],"code":" yields one or more Fragment-like\nobjects directly into the output. Any "},{"id":"/root/children/56/children/3","type":"inlineCode","loc":{"start":9902,"end":9909,"line":{"s":317,"e":317,"code":["objects directly into the output. Any `await`-able function in context is an"]},"column":{"s":38,"e":45}},"dim":["","paragraph.56","inlineCode.3"],"code":"`await`"},{"id":"/root/children/56/children/4","type":"text","loc":{"start":9909,"end":9951,"line":{"s":317,"e":318,"code":["objects directly into the output. Any `await`-able function in context is an","adapter — `search`, `total`, and `_mdt_label` all coexist as named bindings."]},"column":{"s":45,"e":10}},"dim":["","paragraph.56","text.4"],"code":"-able function in context is an\nadapter — "},{"id":"/root/children/56/children/5","type":"inlineCode","loc":{"start":9951,"end":9959,"line":{"s":318,"e":318,"code":["adapter — `search`, `total`, and `_mdt_label` all coexist as named bindings."]},"column":{"s":10,"e":18}},"dim":["","paragraph.56","inlineCode.5"],"code":"`search`"},{"id":"/root/children/56/children/6","type":"text","loc":{"start":9959,"end":9961,"line":{"s":318,"e":318,"code":["adapter — `search`, `total`, and `_mdt_label` all coexist as named bindings."]},"column":{"s":18,"e":20}},"dim":["","paragraph.56","text.6"],"code":", "},{"id":"/root/children/56/children/7","type":"inlineCode","loc":{"start":9961,"end":9968,"line":{"s":318,"e":318,"code":["adapter — `search`, `total`, and `_mdt_label` all coexist as named bindings."]},"column":{"s":20,"e":27}},"dim":["","paragraph.56","inlineCode.7"],"code":"`total`"},{"id":"/root/children/56/children/8","type":"text","loc":{"start":9968,"end":9974,"line":{"s":318,"e":318,"code":["adapter — `search`, `total`, and `_mdt_label` all coexist as named bindings."]},"column":{"s":27,"e":33}},"dim":["","paragraph.56","text.8"],"code":", and "},{"id":"/root/children/56/children/9","type":"inlineCode","loc":{"start":9974,"end":9986,"line":{"s":318,"e":318,"code":["adapter — `search`, `total`, and `_mdt_label` all coexist as named bindings."]},"column":{"s":33,"e":45}},"dim":["","paragraph.56","inlineCode.9"],"code":"`_mdt_label`"},{"id":"/root/children/56/children/10","type":"text","loc":{"start":9986,"end":10017,"line":{"s":318,"e":318,"code":["adapter — `search`, `total`, and `_mdt_label` all coexist as named bindings."]},"column":{"s":45,"e":76}},"dim":["","paragraph.56","text.10"],"code":" all coexist as named bindings."},{"id":"/root/children/57","type":"heading","loc":{"start":10019,"end":10045,"line":{"s":320,"e":320,"code":["### Usage — Error recovery"]},"column":{"s":0,"e":26}},"dim":["","heading.57"],"code":"### Usage — Error recovery","symbName":"heading","symbRange":[10047,10757],"symbRangeL":[320,346],"outerCode":"\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.","outerHtml":"\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>"},{"id":"/root/children/57/children/0","type":"text","loc":{"start":10023,"end":10045,"line":{"s":320,"e":320,"code":["### Usage — Error recovery"]},"column":{"s":4,"e":26}},"dim":["","heading.57","text.0"],"code":"Usage — Error recovery"},{"id":"/root/children/58","type":"paragraph","loc":{"start":10047,"end":10155,"line":{"s":322,"e":323,"code":["When an extruction body throws, `onExtructionError` lets you log and skip","instead of crashing the iteration:"]},"column":{"s":0,"e":34}},"dim":["","paragraph.58"],"code":"When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:"},{"id":"/root/children/58/children/0","type":"text","loc":{"start":10047,"end":10079,"line":{"s":322,"e":322,"code":["When an extruction body throws, `onExtructionError` lets you log and skip"]},"column":{"s":0,"e":32}},"dim":["","paragraph.58","text.0"],"code":"When an extruction body throws, "},{"id":"/root/children/58/children/1","type":"inlineCode","loc":{"start":10079,"end":10098,"line":{"s":322,"e":322,"code":["When an extruction body throws, `onExtructionError` lets you log and skip"]},"column":{"s":32,"e":51}},"dim":["","paragraph.58","inlineCode.1"],"code":"`onExtructionError`"},{"id":"/root/children/58/children/2","type":"text","loc":{"start":10098,"end":10155,"line":{"s":322,"e":323,"code":["When an extruction body throws, `onExtructionError` lets you log and skip","instead of crashing the iteration:"]},"column":{"s":51,"e":34}},"dim":["","paragraph.58","text.2"],"code":" lets you log and skip\ninstead of crashing the iteration:"},{"id":"/root/children/59","type":"code","loc":{"start":10158,"end":10470,"line":{"s":326,"e":340,"code":["```js","const doc = runner({ search }, {","  evalFn: evalBody,","  onExtructionError: (err, headingNode) => {","    console.warn(","      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,","      err.message,","    )","  },","})","","for await (const section of doc) {","  // Sections after the failing extruction still appear","}","```"]},"column":{"s":0,"e":3}},"dim":["","code.59"],"code":"```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```","symbName":"code","symbRange":[10472,10875],"symbRangeL":[null,351],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n"},{"id":"/root/children/60","type":"paragraph","loc":{"start":10472,"end":10757,"line":{"s":342,"e":345,"code":["Without the callback, errors propagate to the consumer's `for await` loop.","With the callback, the failing extruction is silently dropped and iteration","continues with the next heading. The heading node gives access to the","position (`headingNode.position`) for source-mapped diagnostics."]},"column":{"s":0,"e":64}},"dim":["","paragraph.60"],"code":"Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics."},{"id":"/root/children/60/children/0","type":"text","loc":{"start":10472,"end":10529,"line":{"s":342,"e":342,"code":["Without the callback, errors propagate to the consumer's `for await` loop."]},"column":{"s":0,"e":57}},"dim":["","paragraph.60","text.0"],"code":"Without the callback, errors propagate to the consumer's "},{"id":"/root/children/60/children/1","type":"inlineCode","loc":{"start":10529,"end":10540,"line":{"s":342,"e":342,"code":["Without the callback, errors propagate to the consumer's `for await` loop."]},"column":{"s":57,"e":68}},"dim":["","paragraph.60","inlineCode.1"],"code":"`for await`"},{"id":"/root/children/60/children/2","type":"text","loc":{"start":10540,"end":10703,"line":{"s":342,"e":345,"code":["Without the callback, errors propagate to the consumer's `for await` loop.","With the callback, the failing extruction is silently dropped and iteration","continues with the next heading. The heading node gives access to the","position (`headingNode.position`) for source-mapped diagnostics."]},"column":{"s":68,"e":10}},"dim":["","paragraph.60","text.2"],"code":" loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition ("},{"id":"/root/children/60/children/3","type":"inlineCode","loc":{"start":10703,"end":10725,"line":{"s":345,"e":345,"code":["position (`headingNode.position`) for source-mapped diagnostics."]},"column":{"s":10,"e":32}},"dim":["","paragraph.60","inlineCode.3"],"code":"`headingNode.position`"},{"id":"/root/children/60/children/4","type":"text","loc":{"start":10725,"end":10757,"line":{"s":345,"e":345,"code":["position (`headingNode.position`) for source-mapped diagnostics."]},"column":{"s":32,"e":64}},"dim":["","paragraph.60","text.4"],"code":") for source-mapped diagnostics."},{"id":"/root/children/61","type":"heading","loc":{"start":10759,"end":10796,"line":{"s":347,"e":347,"code":["### Usage — Adapter with `_mdt_label`"]},"column":{"s":0,"e":37}},"dim":["","heading.61"],"code":"### Usage — Adapter with `_mdt_label`","symbName":"heading","symbRange":[10798,11544],"symbRangeL":[347,379],"outerCode":"\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.","outerHtml":"\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>"},{"id":"/root/children/61/children/0","type":"text","loc":{"start":10763,"end":10784,"line":{"s":347,"e":347,"code":["### Usage — Adapter with `_mdt_label`"]},"column":{"s":4,"e":25}},"dim":["","heading.61","text.0"],"code":"Usage — Adapter with "},{"id":"/root/children/61/children/1","type":"inlineCode","loc":{"start":10784,"end":10796,"line":{"s":347,"e":347,"code":["### Usage — Adapter with `_mdt_label`"]},"column":{"s":25,"e":37}},"dim":["","heading.61","inlineCode.1"],"code":"`_mdt_label`"},{"id":"/root/children/62","type":"paragraph","loc":{"start":10798,"end":10875,"line":{"s":349,"e":349,"code":["The `_mdt_label` binding lets one adapter serve multiple extruction variants:"]},"column":{"s":0,"e":77}},"dim":["","paragraph.62"],"code":"The `_mdt_label` binding lets one adapter serve multiple extruction variants:"},{"id":"/root/children/62/children/0","type":"text","loc":{"start":10798,"end":10802,"line":{"s":349,"e":349,"code":["The `_mdt_label` binding lets one adapter serve multiple extruction variants:"]},"column":{"s":0,"e":4}},"dim":["","paragraph.62","text.0"],"code":"The "},{"id":"/root/children/62/children/1","type":"inlineCode","loc":{"start":10802,"end":10814,"line":{"s":349,"e":349,"code":["The `_mdt_label` binding lets one adapter serve multiple extruction variants:"]},"column":{"s":4,"e":16}},"dim":["","paragraph.62","inlineCode.1"],"code":"`_mdt_label`"},{"id":"/root/children/62/children/2","type":"text","loc":{"start":10814,"end":10875,"line":{"s":349,"e":349,"code":["The `_mdt_label` binding lets one adapter serve multiple extruction variants:"]},"column":{"s":16,"e":77}},"dim":["","paragraph.62","text.2"],"code":" binding lets one adapter serve multiple extruction variants:"},{"id":"/root/children/63","type":"code","loc":{"start":10878,"end":11424,"line":{"s":352,"e":375,"code":["```js","const md = `# ${search mdd}","","\\`\\`\\`javascript","const items = await search(_mdt_label)","return insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))","\\`\\`\\`","","# ${search js}","","\\`\\`\\`javascript","const items = await search(_mdt_label)","return insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))","\\`\\`\\`","`","","const search = async (q) => {","  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]","  return [{ name: \"main.js\", uri: \"#/main.js\" }]","}","","const runner = compile(md, { remark })","const doc = runner({ search }, { evalFn: evalBody })","```"]},"column":{"s":0,"e":3}},"dim":["","code.63"],"code":"```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```","symbName":"code","symbRange":[11426,11714],"symbRangeL":[null,384],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>"},{"id":"/root/children/64","type":"paragraph","loc":{"start":11426,"end":11544,"line":{"s":377,"e":378,"code":["The same `search` adapter is called with the label as its argument — no need","to hardcode adapter names per extruction."]},"column":{"s":0,"e":41}},"dim":["","paragraph.64"],"code":"The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction."},{"id":"/root/children/64/children/0","type":"text","loc":{"start":11426,"end":11435,"line":{"s":377,"e":377,"code":["The same `search` adapter is called with the label as its argument — no need"]},"column":{"s":0,"e":9}},"dim":["","paragraph.64","text.0"],"code":"The same "},{"id":"/root/children/64/children/1","type":"inlineCode","loc":{"start":11435,"end":11443,"line":{"s":377,"e":377,"code":["The same `search` adapter is called with the label as its argument — no need"]},"column":{"s":9,"e":17}},"dim":["","paragraph.64","inlineCode.1"],"code":"`search`"},{"id":"/root/children/64/children/2","type":"text","loc":{"start":11443,"end":11544,"line":{"s":377,"e":378,"code":["The same `search` adapter is called with the label as its argument — no need","to hardcode adapter names per extruction."]},"column":{"s":17,"e":41}},"dim":["","paragraph.64","text.2"],"code":" adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction."},{"id":"/root/children/65","type":"heading","loc":{"start":11546,"end":11582,"line":{"s":380,"e":380,"code":["### Usage — State across extructions"]},"column":{"s":0,"e":36}},"dim":["","heading.65"],"code":"### Usage — State across extructions","symbName":"heading","symbRange":[11584,13341],"symbRangeL":[380,449],"outerCode":"\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.","outerHtml":"\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>"},{"id":"/root/children/65/children/0","type":"text","loc":{"start":11550,"end":11582,"line":{"s":380,"e":380,"code":["### Usage — State across extructions"]},"column":{"s":4,"e":36}},"dim":["","heading.65","text.0"],"code":"Usage — State across extructions"},{"id":"/root/children/66","type":"paragraph","loc":{"start":11584,"end":11714,"line":{"s":382,"e":383,"code":["The runner automatically injects `mdtState` — a plain object that persists","across extruction evaluations within the same document:"]},"column":{"s":0,"e":55}},"dim":["","paragraph.66"],"code":"The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:"},{"id":"/root/children/66/children/0","type":"text","loc":{"start":11584,"end":11617,"line":{"s":382,"e":382,"code":["The runner automatically injects `mdtState` — a plain object that persists"]},"column":{"s":0,"e":33}},"dim":["","paragraph.66","text.0"],"code":"The runner automatically injects "},{"id":"/root/children/66/children/1","type":"inlineCode","loc":{"start":11617,"end":11627,"line":{"s":382,"e":382,"code":["The runner automatically injects `mdtState` — a plain object that persists"]},"column":{"s":33,"e":43}},"dim":["","paragraph.66","inlineCode.1"],"code":"`mdtState`"},{"id":"/root/children/66/children/2","type":"text","loc":{"start":11627,"end":11714,"line":{"s":382,"e":383,"code":["The runner automatically injects `mdtState` — a plain object that persists","across extruction evaluations within the same document:"]},"column":{"s":43,"e":55}},"dim":["","paragraph.66","text.2"],"code":" — a plain object that persists\nacross extruction evaluations within the same document:"},{"id":"/root/children/67","type":"code","loc":{"start":11716,"end":12351,"line":{"s":385,"e":417,"code":["```js","const md = `# ${init}","","\\`\\`\\`javascript","mdtState.counter = 0","mdtState.items = [\"a\", \"b\", \"c\"]","\\`\\`\\`","","# ${first}","","\\`\\`\\`javascript","mdtState.counter++","return insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )","\\`\\`\\`","","# ${second}","","\\`\\`\\`javascript","mdtState.counter++","return insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )","\\`\\`\\`","`;","","const runner = compile(md, { remark });","const doc = runner({}, { evalFn: evalBody });","","for await (const section of doc) {","  console.log(section.toString());","  // \"${init}\" → transparent (no return/insert)","  // \"${first}\" → \"Item 1: a\"","  // \"${second}\" → \"Item 2: b\"","}","```"]},"column":{"s":0,"e":3}},"dim":["","code.67"],"code":"```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```","symbName":"code","symbRange":[12353,12630],"symbRangeL":[null,424],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>"},{"id":"/root/children/68","type":"paragraph","loc":{"start":12353,"end":12563,"line":{"s":419,"e":421,"code":["`mdtState` is just a `{}` — the extruction body sets properties on it, and","subsequent evaluations read them back. It's automatically available in every","extruction body without being added to the runner context."]},"column":{"s":0,"e":58}},"dim":["","paragraph.68"],"code":"`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context."},{"id":"/root/children/68/children/0","type":"inlineCode","loc":{"start":12353,"end":12363,"line":{"s":419,"e":419,"code":["`mdtState` is just a `{}` — the extruction body sets properties on it, and"]},"column":{"s":0,"e":10}},"dim":["","paragraph.68","inlineCode.0"],"code":"`mdtState`"},{"id":"/root/children/68/children/1","type":"text","loc":{"start":12363,"end":12374,"line":{"s":419,"e":419,"code":["`mdtState` is just a `{}` — the extruction body sets properties on it, and"]},"column":{"s":10,"e":21}},"dim":["","paragraph.68","text.1"],"code":" is just a "},{"id":"/root/children/68/children/2","type":"inlineCode","loc":{"start":12374,"end":12378,"line":{"s":419,"e":419,"code":["`mdtState` is just a `{}` — the extruction body sets properties on it, and"]},"column":{"s":21,"e":25}},"dim":["","paragraph.68","inlineCode.2"],"code":"`{}`"},{"id":"/root/children/68/children/3","type":"text","loc":{"start":12378,"end":12563,"line":{"s":419,"e":421,"code":["`mdtState` is just a `{}` — the extruction body sets properties on it, and","subsequent evaluations read them back. It's automatically available in every","extruction body without being added to the runner context."]},"column":{"s":25,"e":58}},"dim":["","paragraph.68","text.3"],"code":" — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context."},{"id":"/root/children/69","type":"paragraph","loc":{"start":12565,"end":12630,"line":{"s":423,"e":423,"code":["Callers can pre-populate `mdtState` by passing it in the context:"]},"column":{"s":0,"e":65}},"dim":["","paragraph.69"],"code":"Callers can pre-populate `mdtState` by passing it in the context:"},{"id":"/root/children/69/children/0","type":"text","loc":{"start":12565,"end":12590,"line":{"s":423,"e":423,"code":["Callers can pre-populate `mdtState` by passing it in the context:"]},"column":{"s":0,"e":25}},"dim":["","paragraph.69","text.0"],"code":"Callers can pre-populate "},{"id":"/root/children/69/children/1","type":"inlineCode","loc":{"start":12590,"end":12600,"line":{"s":423,"e":423,"code":["Callers can pre-populate `mdtState` by passing it in the context:"]},"column":{"s":25,"e":35}},"dim":["","paragraph.69","inlineCode.1"],"code":"`mdtState`"},{"id":"/root/children/69/children/2","type":"text","loc":{"start":12600,"end":12630,"line":{"s":423,"e":423,"code":["Callers can pre-populate `mdtState` by passing it in the context:"]},"column":{"s":35,"e":65}},"dim":["","paragraph.69","text.2"],"code":" by passing it in the context:"},{"id":"/root/children/70","type":"code","loc":{"start":12632,"end":12741,"line":{"s":425,"e":430,"code":["```js","const doc = runner(","  { mdtState: { repo: \"my-repo\", branch: \"main\" } },","  { evalFn: evalBody },",");","```"]},"column":{"s":0,"e":3}},"dim":["","code.70"],"code":"```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```","symbName":"code","symbRange":[12744,58640],"symbRangeL":[null,432],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n"},{"id":"/root/children/71","type":"code","loc":{"start":12744,"end":12863,"line":{"s":433,"e":439,"code":["```","## ${header}","","\\`\\`\\`javascript","return insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )","\\`\\`\\`","```"]},"column":{"s":0,"e":3}},"dim":["","code.71"],"code":"```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```","symbName":"code","symbRange":[12865,13955],"symbRangeL":[null,466],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n"},{"id":"/root/children/72","type":"paragraph","loc":{"start":12865,"end":12993,"line":{"s":441,"e":442,"code":["This is useful when extructions need shared initialization or cross-section","communication without resorting to global variables."]},"column":{"s":0,"e":52}},"dim":["","paragraph.72"],"code":"This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables."},{"id":"/root/children/72/children/0","type":"text","loc":{"start":12865,"end":12993,"line":{"s":441,"e":442,"code":["This is useful when extructions need shared initialization or cross-section","communication without resorting to global variables."]},"column":{"s":0,"e":52}},"dim":["","paragraph.72","text.0"],"code":"This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables."},{"id":"/root/children/73","type":"paragraph","loc":{"start":12995,"end":13341,"line":{"s":444,"e":448,"code":["**Why this works:** `mdtState` is a single object stored on `runnerContext`.","Each eval call spreads `runnerContext` into the function parameters, but the","spread copies the reference — all evaluations share the same `mdtState` object.","Property mutations (set/add/delete) persist; reassigning `mdtState = ...` would","only affect the local parameter."]},"column":{"s":0,"e":32}},"dim":["","paragraph.73"],"code":"**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter."},{"id":"/root/children/73/children/0","type":"strong","loc":{"start":12995,"end":13014,"line":{"s":444,"e":444,"code":["**Why this works:** `mdtState` is a single object stored on `runnerContext`."]},"column":{"s":0,"e":19}},"dim":["","paragraph.73","strong.0"],"code":"**Why this works:**"},{"id":"/root/children/73/children/0/children/0","type":"text","loc":{"start":12997,"end":13012,"line":{"s":444,"e":444,"code":["**Why this works:** `mdtState` is a single object stored on `runnerContext`."]},"column":{"s":2,"e":17}},"dim":["","paragraph.73","strong.0","text.0"],"code":"Why this works:"},{"id":"/root/children/73/children/1","type":"text","loc":{"start":13014,"end":13015,"line":{"s":444,"e":444,"code":["**Why this works:** `mdtState` is a single object stored on `runnerContext`."]},"column":{"s":19,"e":20}},"dim":["","paragraph.73","text.1"],"code":" "},{"id":"/root/children/73/children/2","type":"inlineCode","loc":{"start":13015,"end":13025,"line":{"s":444,"e":444,"code":["**Why this works:** `mdtState` is a single object stored on `runnerContext`."]},"column":{"s":20,"e":30}},"dim":["","paragraph.73","inlineCode.2"],"code":"`mdtState`"},{"id":"/root/children/73/children/3","type":"text","loc":{"start":13025,"end":13055,"line":{"s":444,"e":444,"code":["**Why this works:** `mdtState` is a single object stored on `runnerContext`."]},"column":{"s":30,"e":60}},"dim":["","paragraph.73","text.3"],"code":" is a single object stored on "},{"id":"/root/children/73/children/4","type":"inlineCode","loc":{"start":13055,"end":13070,"line":{"s":444,"e":444,"code":["**Why this works:** `mdtState` is a single object stored on `runnerContext`."]},"column":{"s":60,"e":75}},"dim":["","paragraph.73","inlineCode.4"],"code":"`runnerContext`"},{"id":"/root/children/73/children/5","type":"text","loc":{"start":13070,"end":13095,"line":{"s":444,"e":445,"code":["**Why this works:** `mdtState` is a single object stored on `runnerContext`.","Each eval call spreads `runnerContext` into the function parameters, but the"]},"column":{"s":75,"e":23}},"dim":["","paragraph.73","text.5"],"code":".\nEach eval call spreads "},{"id":"/root/children/73/children/6","type":"inlineCode","loc":{"start":13095,"end":13110,"line":{"s":445,"e":445,"code":["Each eval call spreads `runnerContext` into the function parameters, but the"]},"column":{"s":23,"e":38}},"dim":["","paragraph.73","inlineCode.6"],"code":"`runnerContext`"},{"id":"/root/children/73/children/7","type":"text","loc":{"start":13110,"end":13210,"line":{"s":445,"e":446,"code":["Each eval call spreads `runnerContext` into the function parameters, but the","spread copies the reference — all evaluations share the same `mdtState` object."]},"column":{"s":38,"e":61}},"dim":["","paragraph.73","text.7"],"code":" into the function parameters, but the\nspread copies the reference — all evaluations share the same "},{"id":"/root/children/73/children/8","type":"inlineCode","loc":{"start":13210,"end":13220,"line":{"s":446,"e":446,"code":["spread copies the reference — all evaluations share the same `mdtState` object."]},"column":{"s":61,"e":71}},"dim":["","paragraph.73","inlineCode.8"],"code":"`mdtState`"},{"id":"/root/children/73/children/9","type":"text","loc":{"start":13220,"end":13286,"line":{"s":446,"e":447,"code":["spread copies the reference — all evaluations share the same `mdtState` object.","Property mutations (set/add/delete) persist; reassigning `mdtState = ...` would"]},"column":{"s":71,"e":57}},"dim":["","paragraph.73","text.9"],"code":" object.\nProperty mutations (set/add/delete) persist; reassigning "},{"id":"/root/children/73/children/10","type":"inlineCode","loc":{"start":13286,"end":13302,"line":{"s":447,"e":447,"code":["Property mutations (set/add/delete) persist; reassigning `mdtState = ...` would"]},"column":{"s":57,"e":73}},"dim":["","paragraph.73","inlineCode.10"],"code":"`mdtState = ...`"},{"id":"/root/children/73/children/11","type":"text","loc":{"start":13302,"end":13341,"line":{"s":447,"e":448,"code":["Property mutations (set/add/delete) persist; reassigning `mdtState = ...` would","only affect the local parameter."]},"column":{"s":73,"e":32}},"dim":["","paragraph.73","text.11"],"code":" would\nonly affect the local parameter."},{"id":"/root/children/74","type":"heading","loc":{"start":13343,"end":13353,"line":{"s":450,"e":450,"code":["### Phases"]},"column":{"s":0,"e":10}},"dim":["","heading.74"],"code":"### Phases","symbName":"heading","symbRange":[13355,13827],"symbRangeL":[450,460],"outerCode":"\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.","outerHtml":"\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>"},{"id":"/root/children/74/children/0","type":"text","loc":{"start":13347,"end":13353,"line":{"s":450,"e":450,"code":["### Phases"]},"column":{"s":4,"e":10}},"dim":["","heading.74","text.0"],"code":"Phases"},{"id":"/root/children/75","type":"paragraph","loc":{"start":13355,"end":13402,"line":{"s":452,"e":452,"code":["The runner materializes the document in phases:"]},"column":{"s":0,"e":47}},"dim":["","paragraph.75"],"code":"The runner materializes the document in phases:"},{"id":"/root/children/75/children/0","type":"text","loc":{"start":13355,"end":13402,"line":{"s":452,"e":452,"code":["The runner materializes the document in phases:"]},"column":{"s":0,"e":47}},"dim":["","paragraph.75","text.0"],"code":"The runner materializes the document in phases:"},{"id":"/root/children/76","type":"paragraph","loc":{"start":13404,"end":13783,"line":{"s":454,"e":457,"code":["| Phase | What's yielded            | Work done                                              |","| ----- | ------------------------- | ------------------------------------------------------ |","| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |","| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |"]},"column":{"s":0,"e":94}},"dim":["","paragraph.76"],"code":"| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |"},{"id":"/root/children/76/children/0","type":"text","loc":{"start":13404,"end":13625,"line":{"s":454,"e":456,"code":["| Phase | What's yielded            | Work done                                              |","| ----- | ------------------------- | ------------------------------------------------------ |","| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |"]},"column":{"s":0,"e":31}},"dim":["","paragraph.76","text.0"],"code":"| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level "},{"id":"/root/children/76/children/1","type":"inlineCode","loc":{"start":13625,"end":13628,"line":{"s":456,"e":456,"code":["| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |"]},"column":{"s":31,"e":34}},"dim":["","paragraph.76","inlineCode.1"],"code":"`#`"},{"id":"/root/children/76/children/2","type":"text","loc":{"start":13628,"end":13783,"line":{"s":456,"e":457,"code":["| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |","| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |"]},"column":{"s":34,"e":94}},"dim":["","paragraph.76","text.2"],"code":") | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |"},{"id":"/root/children/77","type":"paragraph","loc":{"start":13785,"end":13827,"line":{"s":459,"e":459,"code":["No phase happens until the consumer pulls."]},"column":{"s":0,"e":42}},"dim":["","paragraph.77"],"code":"No phase happens until the consumer pulls."},{"id":"/root/children/77/children/0","type":"text","loc":{"start":13785,"end":13827,"line":{"s":459,"e":459,"code":["No phase happens until the consumer pulls."]},"column":{"s":0,"e":42}},"dim":["","paragraph.77","text.0"],"code":"No phase happens until the consumer pulls."},{"id":"/root/children/78","type":"heading","loc":{"start":13829,"end":13840,"line":{"s":461,"e":461,"code":["## Fragment"]},"column":{"s":0,"e":11}},"dim":["","heading.78"],"code":"## Fragment","symbName":"heading","symbRange":[13842,15848],"symbRangeL":[461,503],"outerCode":"\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.","outerHtml":"\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>"},{"id":"/root/children/78/children/0","type":"text","loc":{"start":13832,"end":13840,"line":{"s":461,"e":461,"code":["## Fragment"]},"column":{"s":3,"e":11}},"dim":["","heading.78","text.0"],"code":"Fragment"},{"id":"/root/children/79","type":"paragraph","loc":{"start":13842,"end":13955,"line":{"s":463,"e":464,"code":["A heading + its immediate body content.","A fragment is the core unit the runner yields and the consumer navigates."]},"column":{"s":0,"e":73}},"dim":["","paragraph.79"],"code":"A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates."},{"id":"/root/children/79/children/0","type":"text","loc":{"start":13842,"end":13955,"line":{"s":463,"e":464,"code":["A heading + its immediate body content.","A fragment is the core unit the runner yields and the consumer navigates."]},"column":{"s":0,"e":73}},"dim":["","paragraph.79","text.0"],"code":"A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates."},{"id":"/root/children/80","type":"code","loc":{"start":13958,"end":14466,"line":{"s":467,"e":477,"code":["```js","{","  trail: \"getting-started/installation\", // trail-id identifying this heading","  heading: \"# Chapter 1\",       // raw markdown heading string","  headingLevel: 1,              // number of # characters","  body: \"Some introductory text.\", // canonicalized markdown body (no children)","  hasChildren: true,            // does this fragment have expandable children?","  expand(): AsyncIterable<Fragment>, // yields child fragments","  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body","}","```"]},"column":{"s":0,"e":3}},"dim":["","code.80"],"code":"```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```","symbName":"code","symbRange":[14468,16653],"symbRangeL":[null,528],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n"},{"id":"/root/children/81","type":"list","loc":{"start":14468,"end":15442,"line":{"s":479,"e":495,"code":["- `trail` — the trail-id that uniquely identifies this heading in","  the document hierarchy.","  Computed lazily using the stack algorithm when","  the fragment is first materialized","- `heading` — the heading as markdown source (e.g. `\"## Details\"`)","- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)","- `body` — the immediate body text, **canonicalized**","  (parsed nodes rendered back to markdown).","  Not byte-identical to source: remark normalizes list markers,","  emphasis characters, wrapping.","  If verbatim fidelity is required, use the source position (`node.position`)","  to slice the original text. Does NOT include child fragments.","- `hasChildren` — quick check without triggering expansion","- `expand()` — returns an async iterable of child `Fragment` objects.","  Each child is itself expandable and carries its own trail.","- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as","  markdown. Convenience for getting a fragment's full self-contained markdown."]},"column":{"s":0,"e":78}},"dim":["","list.81"],"code":"- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.","symbName":"list","symbRange":[15444,15926],"symbRangeL":[479,507],"outerCode":"  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:","outerHtml":"<p>  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</p><ul><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>"},{"id":"/root/children/81/children/0","type":"listItem","loc":{"start":14468,"end":14645,"line":{"s":479,"e":482,"code":["- `trail` — the trail-id that uniquely identifies this heading in","  the document hierarchy.","  Computed lazily using the stack algorithm when","  the fragment is first materialized"]},"column":{"s":0,"e":36}},"dim":["","list.81","listItem.0"],"code":"- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized"},{"id":"/root/children/81/children/0/children/0","type":"paragraph","loc":{"start":14470,"end":14645,"line":{"s":479,"e":482,"code":["- `trail` — the trail-id that uniquely identifies this heading in","  the document hierarchy.","  Computed lazily using the stack algorithm when","  the fragment is first materialized"]},"column":{"s":2,"e":36}},"dim":["","list.81","listItem.0","paragraph.0"],"code":"`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized"},{"id":"/root/children/81/children/0/children/0/children/0","type":"inlineCode","loc":{"start":14470,"end":14477,"line":{"s":479,"e":479,"code":["- `trail` — the trail-id that uniquely identifies this heading in"]},"column":{"s":2,"e":9}},"dim":["","list.81","listItem.0","paragraph.0","inlineCode.0"],"code":"`trail`"},{"id":"/root/children/81/children/0/children/0/children/1","type":"text","loc":{"start":14477,"end":14645,"line":{"s":479,"e":482,"code":["- `trail` — the trail-id that uniquely identifies this heading in","  the document hierarchy.","  Computed lazily using the stack algorithm when","  the fragment is first materialized"]},"column":{"s":9,"e":36}},"dim":["","list.81","listItem.0","paragraph.0","text.1"],"code":" — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized"},{"id":"/root/children/81/children/1","type":"listItem","loc":{"start":14646,"end":14712,"line":{"s":483,"e":483,"code":["- `heading` — the heading as markdown source (e.g. `\"## Details\"`)"]},"column":{"s":0,"e":66}},"dim":["","list.81","listItem.1"],"code":"- `heading` — the heading as markdown source (e.g. `\"## Details\"`)"},{"id":"/root/children/81/children/1/children/0","type":"paragraph","loc":{"start":14648,"end":14712,"line":{"s":483,"e":483,"code":["- `heading` — the heading as markdown source (e.g. `\"## Details\"`)"]},"column":{"s":2,"e":66}},"dim":["","list.81","listItem.1","paragraph.0"],"code":"`heading` — the heading as markdown source (e.g. `\"## Details\"`)"},{"id":"/root/children/81/children/1/children/0/children/0","type":"inlineCode","loc":{"start":14648,"end":14657,"line":{"s":483,"e":483,"code":["- `heading` — the heading as markdown source (e.g. `\"## Details\"`)"]},"column":{"s":2,"e":11}},"dim":["","list.81","listItem.1","paragraph.0","inlineCode.0"],"code":"`heading`"},{"id":"/root/children/81/children/1/children/0/children/1","type":"text","loc":{"start":14657,"end":14697,"line":{"s":483,"e":483,"code":["- `heading` — the heading as markdown source (e.g. `\"## Details\"`)"]},"column":{"s":11,"e":51}},"dim":["","list.81","listItem.1","paragraph.0","text.1"],"code":" — the heading as markdown source (e.g. "},{"id":"/root/children/81/children/1/children/0/children/2","type":"inlineCode","loc":{"start":14697,"end":14711,"line":{"s":483,"e":483,"code":["- `heading` — the heading as markdown source (e.g. `\"## Details\"`)"]},"column":{"s":51,"e":65}},"dim":["","list.81","listItem.1","paragraph.0","inlineCode.2"],"code":"`\"## Details\"`"},{"id":"/root/children/81/children/1/children/0/children/3","type":"text","loc":{"start":14711,"end":14712,"line":{"s":483,"e":483,"code":["- `heading` — the heading as markdown source (e.g. `\"## Details\"`)"]},"column":{"s":65,"e":66}},"dim":["","list.81","listItem.1","paragraph.0","text.3"],"code":")"},{"id":"/root/children/81/children/2","type":"listItem","loc":{"start":14713,"end":14767,"line":{"s":484,"e":484,"code":["- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)"]},"column":{"s":0,"e":54}},"dim":["","list.81","listItem.2"],"code":"- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)"},{"id":"/root/children/81/children/2/children/0","type":"paragraph","loc":{"start":14715,"end":14767,"line":{"s":484,"e":484,"code":["- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)"]},"column":{"s":2,"e":54}},"dim":["","list.81","listItem.2","paragraph.0"],"code":"`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)"},{"id":"/root/children/81/children/2/children/0/children/0","type":"inlineCode","loc":{"start":14715,"end":14729,"line":{"s":484,"e":484,"code":["- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)"]},"column":{"s":2,"e":16}},"dim":["","list.81","listItem.2","paragraph.0","inlineCode.0"],"code":"`headingLevel`"},{"id":"/root/children/81/children/2/children/0/children/1","type":"text","loc":{"start":14729,"end":14745,"line":{"s":484,"e":484,"code":["- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)"]},"column":{"s":16,"e":32}},"dim":["","list.81","listItem.2","paragraph.0","text.1"],"code":" — depth (1 for "},{"id":"/root/children/81/children/2/children/0/children/2","type":"inlineCode","loc":{"start":14745,"end":14748,"line":{"s":484,"e":484,"code":["- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)"]},"column":{"s":32,"e":35}},"dim":["","list.81","listItem.2","paragraph.0","inlineCode.2"],"code":"`#`"},{"id":"/root/children/81/children/2/children/0/children/3","type":"text","loc":{"start":14748,"end":14756,"line":{"s":484,"e":484,"code":["- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)"]},"column":{"s":35,"e":43}},"dim":["","list.81","listItem.2","paragraph.0","text.3"],"code":", 2 for "},{"id":"/root/children/81/children/2/children/0/children/4","type":"inlineCode","loc":{"start":14756,"end":14760,"line":{"s":484,"e":484,"code":["- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)"]},"column":{"s":43,"e":47}},"dim":["","list.81","listItem.2","paragraph.0","inlineCode.4"],"code":"`##`"},{"id":"/root/children/81/children/2/children/0/children/5","type":"text","loc":{"start":14760,"end":14767,"line":{"s":484,"e":484,"code":["- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)"]},"column":{"s":47,"e":54}},"dim":["","list.81","listItem.2","paragraph.0","text.5"],"code":", etc.)"},{"id":"/root/children/81/children/3","type":"listItem","loc":{"start":14768,"end":15104,"line":{"s":485,"e":490,"code":["- `body` — the immediate body text, **canonicalized**","  (parsed nodes rendered back to markdown).","  Not byte-identical to source: remark normalizes list markers,","  emphasis characters, wrapping.","  If verbatim fidelity is required, use the source position (`node.position`)","  to slice the original text. Does NOT include child fragments."]},"column":{"s":0,"e":63}},"dim":["","list.81","listItem.3"],"code":"- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments."},{"id":"/root/children/81/children/3/children/0","type":"paragraph","loc":{"start":14770,"end":15104,"line":{"s":485,"e":490,"code":["- `body` — the immediate body text, **canonicalized**","  (parsed nodes rendered back to markdown).","  Not byte-identical to source: remark normalizes list markers,","  emphasis characters, wrapping.","  If verbatim fidelity is required, use the source position (`node.position`)","  to slice the original text. Does NOT include child fragments."]},"column":{"s":2,"e":63}},"dim":["","list.81","listItem.3","paragraph.0"],"code":"`body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments."},{"id":"/root/children/81/children/3/children/0/children/0","type":"inlineCode","loc":{"start":14770,"end":14776,"line":{"s":485,"e":485,"code":["- `body` — the immediate body text, **canonicalized**"]},"column":{"s":2,"e":8}},"dim":["","list.81","listItem.3","paragraph.0","inlineCode.0"],"code":"`body`"},{"id":"/root/children/81/children/3/children/0/children/1","type":"text","loc":{"start":14776,"end":14804,"line":{"s":485,"e":485,"code":["- `body` — the immediate body text, **canonicalized**"]},"column":{"s":8,"e":36}},"dim":["","list.81","listItem.3","paragraph.0","text.1"],"code":" — the immediate body text, "},{"id":"/root/children/81/children/3/children/0/children/2","type":"strong","loc":{"start":14804,"end":14821,"line":{"s":485,"e":485,"code":["- `body` — the immediate body text, **canonicalized**"]},"column":{"s":36,"e":53}},"dim":["","list.81","listItem.3","paragraph.0","strong.2"],"code":"**canonicalized**"},{"id":"/root/children/81/children/3/children/0/children/2/children/0","type":"text","loc":{"start":14806,"end":14819,"line":{"s":485,"e":485,"code":["- `body` — the immediate body text, **canonicalized**"]},"column":{"s":38,"e":51}},"dim":["","list.81","listItem.3","paragraph.0","strong.2","text.0"],"code":"canonicalized"},{"id":"/root/children/81/children/3/children/0/children/3","type":"text","loc":{"start":14821,"end":15024,"line":{"s":485,"e":489,"code":["- `body` — the immediate body text, **canonicalized**","  (parsed nodes rendered back to markdown).","  Not byte-identical to source: remark normalizes list markers,","  emphasis characters, wrapping.","  If verbatim fidelity is required, use the source position (`node.position`)"]},"column":{"s":53,"e":61}},"dim":["","list.81","listItem.3","paragraph.0","text.3"],"code":"\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position ("},{"id":"/root/children/81/children/3/children/0/children/4","type":"inlineCode","loc":{"start":15024,"end":15039,"line":{"s":489,"e":489,"code":["  If verbatim fidelity is required, use the source position (`node.position`)"]},"column":{"s":61,"e":76}},"dim":["","list.81","listItem.3","paragraph.0","inlineCode.4"],"code":"`node.position`"},{"id":"/root/children/81/children/3/children/0/children/5","type":"text","loc":{"start":15039,"end":15104,"line":{"s":489,"e":490,"code":["  If verbatim fidelity is required, use the source position (`node.position`)","  to slice the original text. Does NOT include child fragments."]},"column":{"s":76,"e":63}},"dim":["","list.81","listItem.3","paragraph.0","text.5"],"code":")\n  to slice the original text. Does NOT include child fragments."},{"id":"/root/children/81/children/4","type":"listItem","loc":{"start":15105,"end":15163,"line":{"s":491,"e":491,"code":["- `hasChildren` — quick check without triggering expansion"]},"column":{"s":0,"e":58}},"dim":["","list.81","listItem.4"],"code":"- `hasChildren` — quick check without triggering expansion"},{"id":"/root/children/81/children/4/children/0","type":"paragraph","loc":{"start":15107,"end":15163,"line":{"s":491,"e":491,"code":["- `hasChildren` — quick check without triggering expansion"]},"column":{"s":2,"e":58}},"dim":["","list.81","listItem.4","paragraph.0"],"code":"`hasChildren` — quick check without triggering expansion"},{"id":"/root/children/81/children/4/children/0/children/0","type":"inlineCode","loc":{"start":15107,"end":15120,"line":{"s":491,"e":491,"code":["- `hasChildren` — quick check without triggering expansion"]},"column":{"s":2,"e":15}},"dim":["","list.81","listItem.4","paragraph.0","inlineCode.0"],"code":"`hasChildren`"},{"id":"/root/children/81/children/4/children/0/children/1","type":"text","loc":{"start":15120,"end":15163,"line":{"s":491,"e":491,"code":["- `hasChildren` — quick check without triggering expansion"]},"column":{"s":15,"e":58}},"dim":["","list.81","listItem.4","paragraph.0","text.1"],"code":" — quick check without triggering expansion"},{"id":"/root/children/81/children/5","type":"listItem","loc":{"start":15164,"end":15294,"line":{"s":492,"e":493,"code":["- `expand()` — returns an async iterable of child `Fragment` objects.","  Each child is itself expandable and carries its own trail."]},"column":{"s":0,"e":60}},"dim":["","list.81","listItem.5"],"code":"- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail."},{"id":"/root/children/81/children/5/children/0","type":"paragraph","loc":{"start":15166,"end":15294,"line":{"s":492,"e":493,"code":["- `expand()` — returns an async iterable of child `Fragment` objects.","  Each child is itself expandable and carries its own trail."]},"column":{"s":2,"e":60}},"dim":["","list.81","listItem.5","paragraph.0"],"code":"`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail."},{"id":"/root/children/81/children/5/children/0/children/0","type":"inlineCode","loc":{"start":15166,"end":15176,"line":{"s":492,"e":492,"code":["- `expand()` — returns an async iterable of child `Fragment` objects."]},"column":{"s":2,"e":12}},"dim":["","list.81","listItem.5","paragraph.0","inlineCode.0"],"code":"`expand()`"},{"id":"/root/children/81/children/5/children/0/children/1","type":"text","loc":{"start":15176,"end":15214,"line":{"s":492,"e":492,"code":["- `expand()` — returns an async iterable of child `Fragment` objects."]},"column":{"s":12,"e":50}},"dim":["","list.81","listItem.5","paragraph.0","text.1"],"code":" — returns an async iterable of child "},{"id":"/root/children/81/children/5/children/0/children/2","type":"inlineCode","loc":{"start":15214,"end":15224,"line":{"s":492,"e":492,"code":["- `expand()` — returns an async iterable of child `Fragment` objects."]},"column":{"s":50,"e":60}},"dim":["","list.81","listItem.5","paragraph.0","inlineCode.2"],"code":"`Fragment`"},{"id":"/root/children/81/children/5/children/0/children/3","type":"text","loc":{"start":15224,"end":15294,"line":{"s":492,"e":493,"code":["- `expand()` — returns an async iterable of child `Fragment` objects.","  Each child is itself expandable and carries its own trail."]},"column":{"s":60,"e":60}},"dim":["","list.81","listItem.5","paragraph.0","text.3"],"code":" objects.\n  Each child is itself expandable and carries its own trail."},{"id":"/root/children/81/children/6","type":"listItem","loc":{"start":15295,"end":15442,"line":{"s":494,"e":495,"code":["- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as","  markdown. Convenience for getting a fragment's full self-contained markdown."]},"column":{"s":0,"e":78}},"dim":["","list.81","listItem.6"],"code":"- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown."},{"id":"/root/children/81/children/6/children/0","type":"paragraph","loc":{"start":15297,"end":15442,"line":{"s":494,"e":495,"code":["- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as","  markdown. Convenience for getting a fragment's full self-contained markdown."]},"column":{"s":2,"e":78}},"dim":["","list.81","listItem.6","paragraph.0"],"code":"`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown."},{"id":"/root/children/81/children/6/children/0/children/0","type":"inlineCode","loc":{"start":15297,"end":15309,"line":{"s":494,"e":494,"code":["- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as"]},"column":{"s":2,"e":14}},"dim":["","list.81","listItem.6","paragraph.0","inlineCode.0"],"code":"`toString()`"},{"id":"/root/children/81/children/6/children/0/children/1","type":"text","loc":{"start":15309,"end":15325,"line":{"s":494,"e":494,"code":["- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as"]},"column":{"s":14,"e":30}},"dim":["","list.81","listItem.6","paragraph.0","text.1"],"code":" — concatenates "},{"id":"/root/children/81/children/6/children/0/children/2","type":"inlineCode","loc":{"start":15325,"end":15350,"line":{"s":494,"e":494,"code":["- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as"]},"column":{"s":30,"e":55}},"dim":["","list.81","listItem.6","paragraph.0","inlineCode.2"],"code":"`heading + \"\\n\\n\" + body`"},{"id":"/root/children/81/children/6/children/0/children/3","type":"text","loc":{"start":15350,"end":15442,"line":{"s":494,"e":495,"code":["- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as","  markdown. Convenience for getting a fragment's full self-contained markdown."]},"column":{"s":55,"e":78}},"dim":["","list.81","listItem.6","paragraph.0","text.3"],"code":", rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown."},{"id":"/root/children/82","type":"paragraph","loc":{"start":15444,"end":15848,"line":{"s":497,"e":502,"code":["**AST source:** currently the fragment is materialized from remark's parsed","AST. In the future it could come from the ast-nodes database","(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries","`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.","The fragment shape is designed to be mappable to/from that schema:","`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`."]},"column":{"s":0,"e":58}},"dim":["","paragraph.82"],"code":"**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`."},{"id":"/root/children/82/children/0","type":"strong","loc":{"start":15444,"end":15459,"line":{"s":497,"e":497,"code":["**AST source:** currently the fragment is materialized from remark's parsed"]},"column":{"s":0,"e":15}},"dim":["","paragraph.82","strong.0"],"code":"**AST source:**"},{"id":"/root/children/82/children/0/children/0","type":"text","loc":{"start":15446,"end":15457,"line":{"s":497,"e":497,"code":["**AST source:** currently the fragment is materialized from remark's parsed"]},"column":{"s":2,"e":13}},"dim":["","paragraph.82","strong.0","text.0"],"code":"AST source:"},{"id":"/root/children/82/children/1","type":"text","loc":{"start":15459,"end":15582,"line":{"s":497,"e":499,"code":["**AST source:** currently the fragment is materialized from remark's parsed","AST. In the future it could come from the ast-nodes database","(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries"]},"column":{"s":15,"e":1}},"dim":["","paragraph.82","text.1"],"code":" currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n("},{"id":"/root/children/82/children/2","type":"inlineCode","loc":{"start":15582,"end":15604,"line":{"s":499,"e":499,"code":["(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries"]},"column":{"s":1,"e":23}},"dim":["","paragraph.82","inlineCode.2"],"code":"`cache_ast_lake_nodes`"},{"id":"/root/children/82/children/3","type":"text","loc":{"start":15604,"end":15610,"line":{"s":499,"e":499,"code":["(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries"]},"column":{"s":23,"e":29}},"dim":["","paragraph.82","text.3"],"code":" with "},{"id":"/root/children/82/children/4","type":"inlineCode","loc":{"start":15610,"end":15627,"line":{"s":499,"e":499,"code":["(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries"]},"column":{"s":29,"e":46}},"dim":["","paragraph.82","inlineCode.4"],"code":"`sem = 'heading'`"},{"id":"/root/children/82/children/5","type":"text","loc":{"start":15627,"end":15653,"line":{"s":499,"e":500,"code":["(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries","`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`."]},"column":{"s":46,"e":0}},"dim":["","paragraph.82","text.5"],"code":"), where each row carries\n"},{"id":"/root/children/82/children/6","type":"inlineCode","loc":{"start":15653,"end":15687,"line":{"s":500,"e":500,"code":["`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`."]},"column":{"s":0,"e":34}},"dim":["","paragraph.82","inlineCode.6"],"code":"`{ id, mt, sem, num1, num2, ref }`"},{"id":"/root/children/82/children/7","type":"text","loc":{"start":15687,"end":15692,"line":{"s":500,"e":500,"code":["`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`."]},"column":{"s":34,"e":39}},"dim":["","paragraph.82","text.7"],"code":" and "},{"id":"/root/children/82/children/8","type":"inlineCode","loc":{"start":15692,"end":15699,"line":{"s":500,"e":500,"code":["`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`."]},"column":{"s":39,"e":46}},"dim":["","paragraph.82","inlineCode.8"],"code":"`nomen`"},{"id":"/root/children/82/children/9","type":"text","loc":{"start":15699,"end":15716,"line":{"s":500,"e":500,"code":["`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`."]},"column":{"s":46,"e":63}},"dim":["","paragraph.82","text.9"],"code":" is derived from "},{"id":"/root/children/82/children/10","type":"inlineCode","loc":{"start":15716,"end":15721,"line":{"s":500,"e":500,"code":["`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`."]},"column":{"s":63,"e":68}},"dim":["","paragraph.82","inlineCode.10"],"code":"`ref`"},{"id":"/root/children/82/children/11","type":"text","loc":{"start":15721,"end":15790,"line":{"s":500,"e":502,"code":["`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.","The fragment shape is designed to be mappable to/from that schema:","`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`."]},"column":{"s":68,"e":0}},"dim":["","paragraph.82","text.11"],"code":".\nThe fragment shape is designed to be mappable to/from that schema:\n"},{"id":"/root/children/82/children/12","type":"inlineCode","loc":{"start":15790,"end":15797,"line":{"s":502,"e":502,"code":["`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`."]},"column":{"s":0,"e":7}},"dim":["","paragraph.82","inlineCode.12"],"code":"`trail`"},{"id":"/root/children/82/children/13","type":"text","loc":{"start":15797,"end":15800,"line":{"s":502,"e":502,"code":["`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`."]},"column":{"s":7,"e":10}},"dim":["","paragraph.82","text.13"],"code":" ↔ "},{"id":"/root/children/82/children/14","type":"inlineCode","loc":{"start":15800,"end":15804,"line":{"s":502,"e":502,"code":["`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`."]},"column":{"s":10,"e":14}},"dim":["","paragraph.82","inlineCode.14"],"code":"`id`"},{"id":"/root/children/82/children/15","type":"text","loc":{"start":15804,"end":15806,"line":{"s":502,"e":502,"code":["`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`."]},"column":{"s":14,"e":16}},"dim":["","paragraph.82","text.15"],"code":", "},{"id":"/root/children/82/children/16","type":"inlineCode","loc":{"start":15806,"end":15815,"line":{"s":502,"e":502,"code":["`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`."]},"column":{"s":16,"e":25}},"dim":["","paragraph.82","inlineCode.16"],"code":"`heading`"},{"id":"/root/children/82/children/17","type":"text","loc":{"start":15815,"end":15818,"line":{"s":502,"e":502,"code":["`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`."]},"column":{"s":25,"e":28}},"dim":["","paragraph.82","text.17"],"code":" ↔ "},{"id":"/root/children/82/children/18","type":"inlineCode","loc":{"start":15818,"end":15823,"line":{"s":502,"e":502,"code":["`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`."]},"column":{"s":28,"e":33}},"dim":["","paragraph.82","inlineCode.18"],"code":"`ref`"},{"id":"/root/children/82/children/19","type":"text","loc":{"start":15823,"end":15825,"line":{"s":502,"e":502,"code":["`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`."]},"column":{"s":33,"e":35}},"dim":["","paragraph.82","text.19"],"code":", "},{"id":"/root/children/82/children/20","type":"inlineCode","loc":{"start":15825,"end":15839,"line":{"s":502,"e":502,"code":["`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`."]},"column":{"s":35,"e":49}},"dim":["","paragraph.82","inlineCode.20"],"code":"`headingLevel`"},{"id":"/root/children/82/children/21","type":"text","loc":{"start":15839,"end":15842,"line":{"s":502,"e":502,"code":["`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`."]},"column":{"s":49,"e":52}},"dim":["","paragraph.82","text.21"],"code":" ↔ "},{"id":"/root/children/82/children/22","type":"inlineCode","loc":{"start":15842,"end":15847,"line":{"s":502,"e":502,"code":["`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`."]},"column":{"s":52,"e":57}},"dim":["","paragraph.82","inlineCode.22"],"code":"`sem`"},{"id":"/root/children/82/children/23","type":"text","loc":{"start":15847,"end":15848,"line":{"s":502,"e":502,"code":["`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`."]},"column":{"s":57,"e":58}},"dim":["","paragraph.82","text.23"],"code":"."},{"id":"/root/children/83","type":"heading","loc":{"start":15850,"end":15872,"line":{"s":504,"e":504,"code":["### expand() traversal"]},"column":{"s":0,"e":22}},"dim":["","heading.83"],"code":"### expand() traversal","symbName":"heading","symbRange":[15874,16502],"symbRangeL":[504,520],"outerCode":"\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.","outerHtml":"\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>"},{"id":"/root/children/83/children/0","type":"text","loc":{"start":15854,"end":15872,"line":{"s":504,"e":504,"code":["### expand() traversal"]},"column":{"s":4,"e":22}},"dim":["","heading.83","text.0"],"code":"expand() traversal"},{"id":"/root/children/84","type":"paragraph","loc":{"start":15874,"end":15926,"line":{"s":506,"e":506,"code":["`expand()` walks the remark AST child heading nodes:"]},"column":{"s":0,"e":52}},"dim":["","paragraph.84"],"code":"`expand()` walks the remark AST child heading nodes:"},{"id":"/root/children/84/children/0","type":"inlineCode","loc":{"start":15874,"end":15884,"line":{"s":506,"e":506,"code":["`expand()` walks the remark AST child heading nodes:"]},"column":{"s":0,"e":10}},"dim":["","paragraph.84","inlineCode.0"],"code":"`expand()`"},{"id":"/root/children/84/children/1","type":"text","loc":{"start":15884,"end":15926,"line":{"s":506,"e":506,"code":["`expand()` walks the remark AST child heading nodes:"]},"column":{"s":10,"e":52}},"dim":["","paragraph.84","text.1"],"code":" walks the remark AST child heading nodes:"},{"id":"/root/children/85","type":"list","loc":{"start":15928,"end":16320,"line":{"s":508,"e":515,"code":["1. Walk child nodes left-to-right in document order.","1. When hitting a heading that","   is **not** an extruction → yield a child `Fragment`.","   Its body is the run of non-heading nodes up to","   the next heading at the same level.","1. When hitting an **extruction** heading → skip (inert, no output).","1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current","   fragment's body."]},"column":{"s":0,"e":19}},"dim":["","list.85"],"code":"1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.","symbName":"list","symbRange":[16322,16523],"symbRangeL":[508,522],"outerCode":"1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees","outerHtml":"<ol><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>"},{"id":"/root/children/85/children/0","type":"listItem","loc":{"start":15928,"end":15980,"line":{"s":508,"e":508,"code":["1. Walk child nodes left-to-right in document order."]},"column":{"s":0,"e":52}},"dim":["","list.85","listItem.0"],"code":"1. Walk child nodes left-to-right in document order."},{"id":"/root/children/85/children/0/children/0","type":"paragraph","loc":{"start":15931,"end":15980,"line":{"s":508,"e":508,"code":["1. Walk child nodes left-to-right in document order."]},"column":{"s":3,"e":52}},"dim":["","list.85","listItem.0","paragraph.0"],"code":"Walk child nodes left-to-right in document order."},{"id":"/root/children/85/children/0/children/0/children/0","type":"text","loc":{"start":15931,"end":15980,"line":{"s":508,"e":508,"code":["1. Walk child nodes left-to-right in document order."]},"column":{"s":3,"e":52}},"dim":["","list.85","listItem.0","paragraph.0","text.0"],"code":"Walk child nodes left-to-right in document order."},{"id":"/root/children/85/children/1","type":"listItem","loc":{"start":15981,"end":16156,"line":{"s":509,"e":512,"code":["1. When hitting a heading that","   is **not** an extruction → yield a child `Fragment`.","   Its body is the run of non-heading nodes up to","   the next heading at the same level."]},"column":{"s":0,"e":38}},"dim":["","list.85","listItem.1"],"code":"1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level."},{"id":"/root/children/85/children/1/children/0","type":"paragraph","loc":{"start":15984,"end":16156,"line":{"s":509,"e":512,"code":["1. When hitting a heading that","   is **not** an extruction → yield a child `Fragment`.","   Its body is the run of non-heading nodes up to","   the next heading at the same level."]},"column":{"s":3,"e":38}},"dim":["","list.85","listItem.1","paragraph.0"],"code":"When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level."},{"id":"/root/children/85/children/1/children/0/children/0","type":"text","loc":{"start":15984,"end":16018,"line":{"s":509,"e":510,"code":["1. When hitting a heading that","   is **not** an extruction → yield a child `Fragment`."]},"column":{"s":3,"e":6}},"dim":["","list.85","listItem.1","paragraph.0","text.0"],"code":"When hitting a heading that\n   is "},{"id":"/root/children/85/children/1/children/0/children/1","type":"strong","loc":{"start":16018,"end":16025,"line":{"s":510,"e":510,"code":["   is **not** an extruction → yield a child `Fragment`."]},"column":{"s":6,"e":13}},"dim":["","list.85","listItem.1","paragraph.0","strong.1"],"code":"**not**"},{"id":"/root/children/85/children/1/children/0/children/1/children/0","type":"text","loc":{"start":16020,"end":16023,"line":{"s":510,"e":510,"code":["   is **not** an extruction → yield a child `Fragment`."]},"column":{"s":8,"e":11}},"dim":["","list.85","listItem.1","paragraph.0","strong.1","text.0"],"code":"not"},{"id":"/root/children/85/children/1/children/0/children/2","type":"text","loc":{"start":16025,"end":16056,"line":{"s":510,"e":510,"code":["   is **not** an extruction → yield a child `Fragment`."]},"column":{"s":13,"e":44}},"dim":["","list.85","listItem.1","paragraph.0","text.2"],"code":" an extruction → yield a child "},{"id":"/root/children/85/children/1/children/0/children/3","type":"inlineCode","loc":{"start":16056,"end":16066,"line":{"s":510,"e":510,"code":["   is **not** an extruction → yield a child `Fragment`."]},"column":{"s":44,"e":54}},"dim":["","list.85","listItem.1","paragraph.0","inlineCode.3"],"code":"`Fragment`"},{"id":"/root/children/85/children/1/children/0/children/4","type":"text","loc":{"start":16066,"end":16156,"line":{"s":510,"e":512,"code":["   is **not** an extruction → yield a child `Fragment`.","   Its body is the run of non-heading nodes up to","   the next heading at the same level."]},"column":{"s":54,"e":38}},"dim":["","list.85","listItem.1","paragraph.0","text.4"],"code":".\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level."},{"id":"/root/children/85/children/2","type":"listItem","loc":{"start":16157,"end":16225,"line":{"s":513,"e":513,"code":["1. When hitting an **extruction** heading → skip (inert, no output)."]},"column":{"s":0,"e":68}},"dim":["","list.85","listItem.2"],"code":"1. When hitting an **extruction** heading → skip (inert, no output)."},{"id":"/root/children/85/children/2/children/0","type":"paragraph","loc":{"start":16160,"end":16225,"line":{"s":513,"e":513,"code":["1. When hitting an **extruction** heading → skip (inert, no output)."]},"column":{"s":3,"e":68}},"dim":["","list.85","listItem.2","paragraph.0"],"code":"When hitting an **extruction** heading → skip (inert, no output)."},{"id":"/root/children/85/children/2/children/0/children/0","type":"text","loc":{"start":16160,"end":16176,"line":{"s":513,"e":513,"code":["1. When hitting an **extruction** heading → skip (inert, no output)."]},"column":{"s":3,"e":19}},"dim":["","list.85","listItem.2","paragraph.0","text.0"],"code":"When hitting an "},{"id":"/root/children/85/children/2/children/0/children/1","type":"strong","loc":{"start":16176,"end":16190,"line":{"s":513,"e":513,"code":["1. When hitting an **extruction** heading → skip (inert, no output)."]},"column":{"s":19,"e":33}},"dim":["","list.85","listItem.2","paragraph.0","strong.1"],"code":"**extruction**"},{"id":"/root/children/85/children/2/children/0/children/1/children/0","type":"text","loc":{"start":16178,"end":16188,"line":{"s":513,"e":513,"code":["1. When hitting an **extruction** heading → skip (inert, no output)."]},"column":{"s":21,"e":31}},"dim":["","list.85","listItem.2","paragraph.0","strong.1","text.0"],"code":"extruction"},{"id":"/root/children/85/children/2/children/0/children/2","type":"text","loc":{"start":16190,"end":16225,"line":{"s":513,"e":513,"code":["1. When hitting an **extruction** heading → skip (inert, no output)."]},"column":{"s":33,"e":68}},"dim":["","list.85","listItem.2","paragraph.0","text.2"],"code":" heading → skip (inert, no output)."},{"id":"/root/children/85/children/3","type":"listItem","loc":{"start":16226,"end":16320,"line":{"s":514,"e":515,"code":["1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current","   fragment's body."]},"column":{"s":0,"e":19}},"dim":["","list.85","listItem.3"],"code":"1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body."},{"id":"/root/children/85/children/3/children/0","type":"paragraph","loc":{"start":16229,"end":16320,"line":{"s":514,"e":515,"code":["1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current","   fragment's body."]},"column":{"s":3,"e":19}},"dim":["","list.85","listItem.3","paragraph.0"],"code":"**Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body."},{"id":"/root/children/85/children/3/children/0/children/0","type":"strong","loc":{"start":16229,"end":16244,"line":{"s":514,"e":514,"code":["1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current"]},"column":{"s":3,"e":18}},"dim":["","list.85","listItem.3","paragraph.0","strong.0"],"code":"**Other nodes**"},{"id":"/root/children/85/children/3/children/0/children/0/children/0","type":"text","loc":{"start":16231,"end":16242,"line":{"s":514,"e":514,"code":["1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current"]},"column":{"s":5,"e":16}},"dim":["","list.85","listItem.3","paragraph.0","strong.0","text.0"],"code":"Other nodes"},{"id":"/root/children/85/children/3/children/0/children/1","type":"text","loc":{"start":16244,"end":16320,"line":{"s":514,"e":515,"code":["1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current","   fragment's body."]},"column":{"s":18,"e":19}},"dim":["","list.85","listItem.3","paragraph.0","text.1"],"code":" (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body."},{"id":"/root/children/86","type":"paragraph","loc":{"start":16322,"end":16502,"line":{"s":517,"e":519,"code":["**Body boundary rule:** content before the first child heading belongs to","the parent's `body`; content between child heading _N_ and","the next heading belongs to child _N_'s `body`."]},"column":{"s":0,"e":47}},"dim":["","paragraph.86"],"code":"**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`."},{"id":"/root/children/86/children/0","type":"strong","loc":{"start":16322,"end":16345,"line":{"s":517,"e":517,"code":["**Body boundary rule:** content before the first child heading belongs to"]},"column":{"s":0,"e":23}},"dim":["","paragraph.86","strong.0"],"code":"**Body boundary rule:**"},{"id":"/root/children/86/children/0/children/0","type":"text","loc":{"start":16324,"end":16343,"line":{"s":517,"e":517,"code":["**Body boundary rule:** content before the first child heading belongs to"]},"column":{"s":2,"e":21}},"dim":["","paragraph.86","strong.0","text.0"],"code":"Body boundary rule:"},{"id":"/root/children/86/children/1","type":"text","loc":{"start":16345,"end":16409,"line":{"s":517,"e":518,"code":["**Body boundary rule:** content before the first child heading belongs to","the parent's `body`; content between child heading _N_ and"]},"column":{"s":23,"e":13}},"dim":["","paragraph.86","text.1"],"code":" content before the first child heading belongs to\nthe parent's "},{"id":"/root/children/86/children/2","type":"inlineCode","loc":{"start":16409,"end":16415,"line":{"s":518,"e":518,"code":["the parent's `body`; content between child heading _N_ and"]},"column":{"s":13,"e":19}},"dim":["","paragraph.86","inlineCode.2"],"code":"`body`"},{"id":"/root/children/86/children/3","type":"text","loc":{"start":16415,"end":16447,"line":{"s":518,"e":518,"code":["the parent's `body`; content between child heading _N_ and"]},"column":{"s":19,"e":51}},"dim":["","paragraph.86","text.3"],"code":"; content between child heading "},{"id":"/root/children/86/children/4","type":"emphasis","loc":{"start":16447,"end":16450,"line":{"s":518,"e":518,"code":["the parent's `body`; content between child heading _N_ and"]},"column":{"s":51,"e":54}},"dim":["","paragraph.86","emphasis.4"],"code":"_N_"},{"id":"/root/children/86/children/4/children/0","type":"text","loc":{"start":16448,"end":16449,"line":{"s":518,"e":518,"code":["the parent's `body`; content between child heading _N_ and"]},"column":{"s":52,"e":53}},"dim":["","paragraph.86","emphasis.4","text.0"],"code":"N"},{"id":"/root/children/86/children/5","type":"text","loc":{"start":16450,"end":16489,"line":{"s":518,"e":519,"code":["the parent's `body`; content between child heading _N_ and","the next heading belongs to child _N_'s `body`."]},"column":{"s":54,"e":34}},"dim":["","paragraph.86","text.5"],"code":" and\nthe next heading belongs to child "},{"id":"/root/children/86/children/6","type":"emphasis","loc":{"start":16489,"end":16492,"line":{"s":519,"e":519,"code":["the next heading belongs to child _N_'s `body`."]},"column":{"s":34,"e":37}},"dim":["","paragraph.86","emphasis.6"],"code":"_N_"},{"id":"/root/children/86/children/6/children/0","type":"text","loc":{"start":16490,"end":16491,"line":{"s":519,"e":519,"code":["the next heading belongs to child _N_'s `body`."]},"column":{"s":35,"e":36}},"dim":["","paragraph.86","emphasis.6","text.0"],"code":"N"},{"id":"/root/children/86/children/7","type":"text","loc":{"start":16492,"end":16495,"line":{"s":519,"e":519,"code":["the next heading belongs to child _N_'s `body`."]},"column":{"s":37,"e":40}},"dim":["","paragraph.86","text.7"],"code":"'s "},{"id":"/root/children/86/children/8","type":"inlineCode","loc":{"start":16495,"end":16501,"line":{"s":519,"e":519,"code":["the next heading belongs to child _N_'s `body`."]},"column":{"s":40,"e":46}},"dim":["","paragraph.86","inlineCode.8"],"code":"`body`"},{"id":"/root/children/86/children/9","type":"text","loc":{"start":16501,"end":16502,"line":{"s":519,"e":519,"code":["the next heading belongs to child _N_'s `body`."]},"column":{"s":46,"e":47}},"dim":["","paragraph.86","text.9"],"code":"."},{"id":"/root/children/87","type":"heading","loc":{"start":16504,"end":16523,"line":{"s":521,"e":521,"code":["### Lazy guarantees"]},"column":{"s":0,"e":19}},"dim":["","heading.87"],"code":"### Lazy guarantees","symbName":"heading","symbRange":[16525,16638],"symbRangeL":[521,525],"outerCode":"\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments","outerHtml":"\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>"},{"id":"/root/children/87/children/0","type":"text","loc":{"start":16508,"end":16523,"line":{"s":521,"e":521,"code":["### Lazy guarantees"]},"column":{"s":4,"e":19}},"dim":["","heading.87","text.0"],"code":"Lazy guarantees"},{"id":"/root/children/88","type":"list","loc":{"start":16525,"end":16638,"line":{"s":523,"e":524,"code":["- `expand()` does nothing until iterated","- Iterating past the first few fragments doesn't process later fragments"]},"column":{"s":0,"e":72}},"dim":["","list.88"],"code":"- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments","symbName":"list","symbRange":[16640,18345],"symbRangeL":[523,567],"outerCode":"- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):","outerHtml":"<ul><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>"},{"id":"/root/children/88/children/0","type":"listItem","loc":{"start":16525,"end":16565,"line":{"s":523,"e":523,"code":["- `expand()` does nothing until iterated"]},"column":{"s":0,"e":40}},"dim":["","list.88","listItem.0"],"code":"- `expand()` does nothing until iterated"},{"id":"/root/children/88/children/0/children/0","type":"paragraph","loc":{"start":16527,"end":16565,"line":{"s":523,"e":523,"code":["- `expand()` does nothing until iterated"]},"column":{"s":2,"e":40}},"dim":["","list.88","listItem.0","paragraph.0"],"code":"`expand()` does nothing until iterated"},{"id":"/root/children/88/children/0/children/0/children/0","type":"inlineCode","loc":{"start":16527,"end":16537,"line":{"s":523,"e":523,"code":["- `expand()` does nothing until iterated"]},"column":{"s":2,"e":12}},"dim":["","list.88","listItem.0","paragraph.0","inlineCode.0"],"code":"`expand()`"},{"id":"/root/children/88/children/0/children/0/children/1","type":"text","loc":{"start":16537,"end":16565,"line":{"s":523,"e":523,"code":["- `expand()` does nothing until iterated"]},"column":{"s":12,"e":40}},"dim":["","list.88","listItem.0","paragraph.0","text.1"],"code":" does nothing until iterated"},{"id":"/root/children/88/children/1","type":"listItem","loc":{"start":16566,"end":16638,"line":{"s":524,"e":524,"code":["- Iterating past the first few fragments doesn't process later fragments"]},"column":{"s":0,"e":72}},"dim":["","list.88","listItem.1"],"code":"- Iterating past the first few fragments doesn't process later fragments"},{"id":"/root/children/88/children/1/children/0","type":"paragraph","loc":{"start":16568,"end":16638,"line":{"s":524,"e":524,"code":["- Iterating past the first few fragments doesn't process later fragments"]},"column":{"s":2,"e":72}},"dim":["","list.88","listItem.1","paragraph.0"],"code":"Iterating past the first few fragments doesn't process later fragments"},{"id":"/root/children/88/children/1/children/0/children/0","type":"text","loc":{"start":16568,"end":16638,"line":{"s":524,"e":524,"code":["- Iterating past the first few fragments doesn't process later fragments"]},"column":{"s":2,"e":72}},"dim":["","list.88","listItem.1","paragraph.0","text.0"],"code":"Iterating past the first few fragments doesn't process later fragments"},{"id":"/root/children/89","type":"heading","loc":{"start":16640,"end":16653,"line":{"s":526,"e":526,"code":["## Extruction"]},"column":{"s":0,"e":13}},"dim":["","heading.89"],"code":"## Extruction","symbName":"heading","symbRange":[16656,17650],"symbRangeL":[526,551],"outerCode":"\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.","outerHtml":"\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>"},{"id":"/root/children/89/children/0","type":"text","loc":{"start":16643,"end":16653,"line":{"s":526,"e":526,"code":["## Extruction"]},"column":{"s":3,"e":13}},"dim":["","heading.89","text.0"],"code":"Extruction"},{"id":"/root/children/90","type":"code","loc":{"start":16656,"end":16755,"line":{"s":529,"e":535,"code":["```","## ${label}","","\\`\\`\\`javascript","// body code — only ```javascript blocks are evaluated","\\`\\`\\`","```"]},"column":{"s":0,"e":3}},"dim":["","code.90"],"code":"```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```","symbName":"code","symbRange":[16757,23562],"symbRangeL":[null,676],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n"},{"id":"/root/children/91","type":"paragraph","loc":{"start":16757,"end":17048,"line":{"s":537,"e":541,"code":["An extruction is a `# ${...}` heading.","When `evalFn` is provided, the body is evaluated as JavaScript —","but **only code inside ` ```javascript ` code blocks** is extracted.","Any other markdown content in the body is ignored.","Without `evalFn`, the extruction and its body are silently dropped."]},"column":{"s":0,"e":67}},"dim":["","paragraph.91"],"code":"An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped."},{"id":"/root/children/91/children/0","type":"text","loc":{"start":16757,"end":16776,"line":{"s":537,"e":537,"code":["An extruction is a `# ${...}` heading."]},"column":{"s":0,"e":19}},"dim":["","paragraph.91","text.0"],"code":"An extruction is a "},{"id":"/root/children/91/children/1","type":"inlineCode","loc":{"start":16776,"end":16786,"line":{"s":537,"e":537,"code":["An extruction is a `# ${...}` heading."]},"column":{"s":19,"e":29}},"dim":["","paragraph.91","inlineCode.1"],"code":"`# ${...}`"},{"id":"/root/children/91/children/2","type":"text","loc":{"start":16786,"end":16801,"line":{"s":537,"e":538,"code":["An extruction is a `# ${...}` heading.","When `evalFn` is provided, the body is evaluated as JavaScript —"]},"column":{"s":29,"e":5}},"dim":["","paragraph.91","text.2"],"code":" heading.\nWhen "},{"id":"/root/children/91/children/3","type":"inlineCode","loc":{"start":16801,"end":16809,"line":{"s":538,"e":538,"code":["When `evalFn` is provided, the body is evaluated as JavaScript —"]},"column":{"s":5,"e":13}},"dim":["","paragraph.91","inlineCode.3"],"code":"`evalFn`"},{"id":"/root/children/91/children/4","type":"text","loc":{"start":16809,"end":16865,"line":{"s":538,"e":539,"code":["When `evalFn` is provided, the body is evaluated as JavaScript —","but **only code inside ` ```javascript ` code blocks** is extracted."]},"column":{"s":13,"e":4}},"dim":["","paragraph.91","text.4"],"code":" is provided, the body is evaluated as JavaScript —\nbut "},{"id":"/root/children/91/children/5","type":"strong","loc":{"start":16865,"end":16915,"line":{"s":539,"e":539,"code":["but **only code inside ` ```javascript ` code blocks** is extracted."]},"column":{"s":4,"e":54}},"dim":["","paragraph.91","strong.5"],"code":"**only code inside ` ```javascript ` code blocks**"},{"id":"/root/children/91/children/5/children/0","type":"text","loc":{"start":16867,"end":16884,"line":{"s":539,"e":539,"code":["but **only code inside ` ```javascript ` code blocks** is extracted."]},"column":{"s":6,"e":23}},"dim":["","paragraph.91","strong.5","text.0"],"code":"only code inside "},{"id":"/root/children/91/children/5/children/1","type":"inlineCode","loc":{"start":16884,"end":16901,"line":{"s":539,"e":539,"code":["but **only code inside ` ```javascript ` code blocks** is extracted."]},"column":{"s":23,"e":40}},"dim":["","paragraph.91","strong.5","inlineCode.1"],"code":"` ```javascript `"},{"id":"/root/children/91/children/5/children/2","type":"text","loc":{"start":16901,"end":16913,"line":{"s":539,"e":539,"code":["but **only code inside ` ```javascript ` code blocks** is extracted."]},"column":{"s":40,"e":52}},"dim":["","paragraph.91","strong.5","text.2"],"code":" code blocks"},{"id":"/root/children/91/children/6","type":"text","loc":{"start":16915,"end":16989,"line":{"s":539,"e":541,"code":["but **only code inside ` ```javascript ` code blocks** is extracted.","Any other markdown content in the body is ignored.","Without `evalFn`, the extruction and its body are silently dropped."]},"column":{"s":54,"e":8}},"dim":["","paragraph.91","text.6"],"code":" is extracted.\nAny other markdown content in the body is ignored.\nWithout "},{"id":"/root/children/91/children/7","type":"inlineCode","loc":{"start":16989,"end":16997,"line":{"s":541,"e":541,"code":["Without `evalFn`, the extruction and its body are silently dropped."]},"column":{"s":8,"e":16}},"dim":["","paragraph.91","inlineCode.7"],"code":"`evalFn`"},{"id":"/root/children/91/children/8","type":"text","loc":{"start":16997,"end":17048,"line":{"s":541,"e":541,"code":["Without `evalFn`, the extruction and its body are silently dropped."]},"column":{"s":16,"e":67}},"dim":["","paragraph.91","text.8"],"code":", the extruction and its body are silently dropped."},{"id":"/root/children/92","type":"paragraph","loc":{"start":17050,"end":17529,"line":{"s":543,"e":547,"code":["| Property  | Value                                                                           |","| --------- | ------------------------------------------------------------------------------- |","| Detection | Heading text starts with `${`                                                   |","| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |","| Effect    | Removed from output; children promoted                                          |"]},"column":{"s":0,"e":95}},"dim":["","paragraph.92"],"code":"| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |"},{"id":"/root/children/92/children/0","type":"text","loc":{"start":17050,"end":17281,"line":{"s":543,"e":545,"code":["| Property  | Value                                                                           |","| --------- | ------------------------------------------------------------------------------- |","| Detection | Heading text starts with `${`                                                   |"]},"column":{"s":0,"e":39}},"dim":["","paragraph.92","text.0"],"code":"| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with "},{"id":"/root/children/92/children/1","type":"inlineCode","loc":{"start":17281,"end":17285,"line":{"s":545,"e":545,"code":["| Detection | Heading text starts with `${`                                                   |"]},"column":{"s":39,"e":43}},"dim":["","paragraph.92","inlineCode.1"],"code":"`${`"},{"id":"/root/children/92/children/2","type":"text","loc":{"start":17285,"end":17371,"line":{"s":545,"e":546,"code":["| Detection | Heading text starts with `${`                                                   |","| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |"]},"column":{"s":43,"e":33}},"dim":["","paragraph.92","text.2"],"code":"                                                   |\n| Body      | JavaScript code in "},{"id":"/root/children/92/children/3","type":"inlineCode","loc":{"start":17371,"end":17388,"line":{"s":546,"e":546,"code":["| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |"]},"column":{"s":33,"e":50}},"dim":["","paragraph.92","inlineCode.3"],"code":"` ```javascript `"},{"id":"/root/children/92/children/4","type":"text","loc":{"start":17388,"end":17529,"line":{"s":546,"e":547,"code":["| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |","| Effect    | Removed from output; children promoted                                          |"]},"column":{"s":50,"e":95}},"dim":["","paragraph.92","text.4"],"code":" code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |"},{"id":"/root/children/93","type":"paragraph","loc":{"start":17531,"end":17650,"line":{"s":549,"e":550,"code":["The `data.label` (text between `${}`) is available on the heading node for","future processing but has no current effect."]},"column":{"s":0,"e":44}},"dim":["","paragraph.93"],"code":"The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect."},{"id":"/root/children/93/children/0","type":"text","loc":{"start":17531,"end":17535,"line":{"s":549,"e":549,"code":["The `data.label` (text between `${}`) is available on the heading node for"]},"column":{"s":0,"e":4}},"dim":["","paragraph.93","text.0"],"code":"The "},{"id":"/root/children/93/children/1","type":"inlineCode","loc":{"start":17535,"end":17547,"line":{"s":549,"e":549,"code":["The `data.label` (text between `${}`) is available on the heading node for"]},"column":{"s":4,"e":16}},"dim":["","paragraph.93","inlineCode.1"],"code":"`data.label`"},{"id":"/root/children/93/children/2","type":"text","loc":{"start":17547,"end":17562,"line":{"s":549,"e":549,"code":["The `data.label` (text between `${}`) is available on the heading node for"]},"column":{"s":16,"e":31}},"dim":["","paragraph.93","text.2"],"code":" (text between "},{"id":"/root/children/93/children/3","type":"inlineCode","loc":{"start":17562,"end":17567,"line":{"s":549,"e":549,"code":["The `data.label` (text between `${}`) is available on the heading node for"]},"column":{"s":31,"e":36}},"dim":["","paragraph.93","inlineCode.3"],"code":"`${}`"},{"id":"/root/children/93/children/4","type":"text","loc":{"start":17567,"end":17650,"line":{"s":549,"e":550,"code":["The `data.label` (text between `${}`) is available on the heading node for","future processing but has no current effect."]},"column":{"s":36,"e":44}},"dim":["","paragraph.93","text.4"],"code":") is available on the heading node for\nfuture processing but has no current effect."},{"id":"/root/children/94","type":"heading","loc":{"start":17652,"end":17678,"line":{"s":552,"e":552,"code":["### Transparency semantics"]},"column":{"s":0,"e":26}},"dim":["","heading.94"],"code":"### Transparency semantics","symbName":"heading","symbRange":[17680,18283],"symbRangeL":[552,563],"outerCode":"\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.","outerHtml":"\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>"},{"id":"/root/children/94/children/0","type":"text","loc":{"start":17656,"end":17678,"line":{"s":552,"e":552,"code":["### Transparency semantics"]},"column":{"s":4,"e":26}},"dim":["","heading.94","text.0"],"code":"Transparency semantics"},{"id":"/root/children/95","type":"paragraph","loc":{"start":17680,"end":17978,"line":{"s":554,"e":557,"code":["Extructions are **fully transparent** — they produce no output and their","body content is silently dropped, but non-extruction headings nested under","an extruction are **promoted** to the nearest non-extruction ancestor's","`expand()` output. Their trail is computed as if the extruction doesn't exist."]},"column":{"s":0,"e":78}},"dim":["","paragraph.95"],"code":"Extructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist."},{"id":"/root/children/95/children/0","type":"text","loc":{"start":17680,"end":17696,"line":{"s":554,"e":554,"code":["Extructions are **fully transparent** — they produce no output and their"]},"column":{"s":0,"e":16}},"dim":["","paragraph.95","text.0"],"code":"Extructions are "},{"id":"/root/children/95/children/1","type":"strong","loc":{"start":17696,"end":17717,"line":{"s":554,"e":554,"code":["Extructions are **fully transparent** — they produce no output and their"]},"column":{"s":16,"e":37}},"dim":["","paragraph.95","strong.1"],"code":"**fully transparent**"},{"id":"/root/children/95/children/1/children/0","type":"text","loc":{"start":17698,"end":17715,"line":{"s":554,"e":554,"code":["Extructions are **fully transparent** — they produce no output and their"]},"column":{"s":18,"e":35}},"dim":["","paragraph.95","strong.1","text.0"],"code":"fully transparent"},{"id":"/root/children/95/children/2","type":"text","loc":{"start":17717,"end":17846,"line":{"s":554,"e":556,"code":["Extructions are **fully transparent** — they produce no output and their","body content is silently dropped, but non-extruction headings nested under","an extruction are **promoted** to the nearest non-extruction ancestor's"]},"column":{"s":37,"e":18}},"dim":["","paragraph.95","text.2"],"code":" — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are "},{"id":"/root/children/95/children/3","type":"strong","loc":{"start":17846,"end":17858,"line":{"s":556,"e":556,"code":["an extruction are **promoted** to the nearest non-extruction ancestor's"]},"column":{"s":18,"e":30}},"dim":["","paragraph.95","strong.3"],"code":"**promoted**"},{"id":"/root/children/95/children/3/children/0","type":"text","loc":{"start":17848,"end":17856,"line":{"s":556,"e":556,"code":["an extruction are **promoted** to the nearest non-extruction ancestor's"]},"column":{"s":20,"e":28}},"dim":["","paragraph.95","strong.3","text.0"],"code":"promoted"},{"id":"/root/children/95/children/4","type":"text","loc":{"start":17858,"end":17900,"line":{"s":556,"e":557,"code":["an extruction are **promoted** to the nearest non-extruction ancestor's","`expand()` output. Their trail is computed as if the extruction doesn't exist."]},"column":{"s":30,"e":0}},"dim":["","paragraph.95","text.4"],"code":" to the nearest non-extruction ancestor's\n"},{"id":"/root/children/95/children/5","type":"inlineCode","loc":{"start":17900,"end":17910,"line":{"s":557,"e":557,"code":["`expand()` output. Their trail is computed as if the extruction doesn't exist."]},"column":{"s":0,"e":10}},"dim":["","paragraph.95","inlineCode.5"],"code":"`expand()`"},{"id":"/root/children/95/children/6","type":"text","loc":{"start":17910,"end":17978,"line":{"s":557,"e":557,"code":["`expand()` output. Their trail is computed as if the extruction doesn't exist."]},"column":{"s":10,"e":78}},"dim":["","paragraph.95","text.6"],"code":" output. Their trail is computed as if the extruction doesn't exist."},{"id":"/root/children/96","type":"paragraph","loc":{"start":17980,"end":18283,"line":{"s":559,"e":562,"code":["Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past","an extruction's non-heading content but stops at any heading (a promoted child),","rather than skipping the entire subtree. This is used by `expandChildren`,","`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency."]},"column":{"s":0,"e":72}},"dim":["","paragraph.96"],"code":"Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency."},{"id":"/root/children/96/children/0","type":"text","loc":{"start":17980,"end":17996,"line":{"s":559,"e":559,"code":["Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past"]},"column":{"s":0,"e":16}},"dim":["","paragraph.96","text.0"],"code":"Implementation: "},{"id":"/root/children/96/children/1","type":"inlineCode","loc":{"start":17996,"end":18040,"line":{"s":559,"e":559,"code":["Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past"]},"column":{"s":16,"e":60}},"dim":["","paragraph.96","inlineCode.1"],"code":"`skipExtructionBody(startIdx, rootChildren)`"},{"id":"/root/children/96/children/2","type":"text","loc":{"start":18040,"end":18193,"line":{"s":559,"e":561,"code":["Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past","an extruction's non-heading content but stops at any heading (a promoted child),","rather than skipping the entire subtree. This is used by `expandChildren`,"]},"column":{"s":60,"e":57}},"dim":["","paragraph.96","text.2"],"code":" advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by "},{"id":"/root/children/96/children/3","type":"inlineCode","loc":{"start":18193,"end":18209,"line":{"s":561,"e":561,"code":["rather than skipping the entire subtree. This is used by `expandChildren`,"]},"column":{"s":57,"e":73}},"dim":["","paragraph.96","inlineCode.3"],"code":"`expandChildren`"},{"id":"/root/children/96/children/4","type":"text","loc":{"start":18209,"end":18211,"line":{"s":561,"e":562,"code":["rather than skipping the entire subtree. This is used by `expandChildren`,","`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency."]},"column":{"s":73,"e":0}},"dim":["","paragraph.96","text.4"],"code":",\n"},{"id":"/root/children/96/children/5","type":"inlineCode","loc":{"start":18211,"end":18229,"line":{"s":562,"e":562,"code":["`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency."]},"column":{"s":0,"e":18}},"dim":["","paragraph.96","inlineCode.5"],"code":"`collectBodyNodes`"},{"id":"/root/children/96/children/6","type":"text","loc":{"start":18229,"end":18235,"line":{"s":562,"e":562,"code":["`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency."]},"column":{"s":18,"e":24}},"dim":["","paragraph.96","text.6"],"code":", and "},{"id":"/root/children/96/children/7","type":"inlineCode","loc":{"start":18235,"end":18258,"line":{"s":562,"e":562,"code":["`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency."]},"column":{"s":24,"e":47}},"dim":["","paragraph.96","inlineCode.7"],"code":"`hasNonExtructionChild`"},{"id":"/root/children/96/children/8","type":"text","loc":{"start":18258,"end":18283,"line":{"s":562,"e":562,"code":["`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency."]},"column":{"s":47,"e":72}},"dim":["","paragraph.96","text.8"],"code":" to maintain consistency."},{"id":"/root/children/97","type":"heading","loc":{"start":18285,"end":18302,"line":{"s":564,"e":564,"code":["## Error Handling"]},"column":{"s":0,"e":17}},"dim":["","heading.97"],"code":"## Error Handling","symbName":"heading","symbRange":[18304,19680],"symbRangeL":[564,587],"outerCode":"\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.","outerHtml":"\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>"},{"id":"/root/children/97/children/0","type":"text","loc":{"start":18288,"end":18302,"line":{"s":564,"e":564,"code":["## Error Handling"]},"column":{"s":3,"e":17}},"dim":["","heading.97","text.0"],"code":"Error Handling"},{"id":"/root/children/98","type":"paragraph","loc":{"start":18304,"end":18345,"line":{"s":566,"e":566,"code":["**Compile-time** (thrown by `compile()`):"]},"column":{"s":0,"e":41}},"dim":["","paragraph.98"],"code":"**Compile-time** (thrown by `compile()`):"},{"id":"/root/children/98/children/0","type":"strong","loc":{"start":18304,"end":18320,"line":{"s":566,"e":566,"code":["**Compile-time** (thrown by `compile()`):"]},"column":{"s":0,"e":16}},"dim":["","paragraph.98","strong.0"],"code":"**Compile-time**"},{"id":"/root/children/98/children/0/children/0","type":"text","loc":{"start":18306,"end":18318,"line":{"s":566,"e":566,"code":["**Compile-time** (thrown by `compile()`):"]},"column":{"s":2,"e":14}},"dim":["","paragraph.98","strong.0","text.0"],"code":"Compile-time"},{"id":"/root/children/98/children/1","type":"text","loc":{"start":18320,"end":18332,"line":{"s":566,"e":566,"code":["**Compile-time** (thrown by `compile()`):"]},"column":{"s":16,"e":28}},"dim":["","paragraph.98","text.1"],"code":" (thrown by "},{"id":"/root/children/98/children/2","type":"inlineCode","loc":{"start":18332,"end":18343,"line":{"s":566,"e":566,"code":["**Compile-time** (thrown by `compile()`):"]},"column":{"s":28,"e":39}},"dim":["","paragraph.98","inlineCode.2"],"code":"`compile()`"},{"id":"/root/children/98/children/3","type":"text","loc":{"start":18343,"end":18345,"line":{"s":566,"e":566,"code":["**Compile-time** (thrown by `compile()`):"]},"column":{"s":39,"e":41}},"dim":["","paragraph.98","text.3"],"code":"):"},{"id":"/root/children/99","type":"list","loc":{"start":18347,"end":18392,"line":{"s":568,"e":568,"code":["- Unparseable markdown (remark parse failure)"]},"column":{"s":0,"e":45}},"dim":["","list.99"],"code":"- Unparseable markdown (remark parse failure)","symbName":"list","symbRange":[18394,18447],"symbRangeL":[568,571],"outerCode":"\n**Runtime** (caught by `onExtructionError` callback):","outerHtml":"\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>"},{"id":"/root/children/99/children/0","type":"listItem","loc":{"start":18347,"end":18392,"line":{"s":568,"e":568,"code":["- Unparseable markdown (remark parse failure)"]},"column":{"s":0,"e":45}},"dim":["","list.99","listItem.0"],"code":"- Unparseable markdown (remark parse failure)"},{"id":"/root/children/99/children/0/children/0","type":"paragraph","loc":{"start":18349,"end":18392,"line":{"s":568,"e":568,"code":["- Unparseable markdown (remark parse failure)"]},"column":{"s":2,"e":45}},"dim":["","list.99","listItem.0","paragraph.0"],"code":"Unparseable markdown (remark parse failure)"},{"id":"/root/children/99/children/0/children/0/children/0","type":"text","loc":{"start":18349,"end":18392,"line":{"s":568,"e":568,"code":["- Unparseable markdown (remark parse failure)"]},"column":{"s":2,"e":45}},"dim":["","list.99","listItem.0","paragraph.0","text.0"],"code":"Unparseable markdown (remark parse failure)"},{"id":"/root/children/100","type":"paragraph","loc":{"start":18394,"end":18447,"line":{"s":570,"e":570,"code":["**Runtime** (caught by `onExtructionError` callback):"]},"column":{"s":0,"e":53}},"dim":["","paragraph.100"],"code":"**Runtime** (caught by `onExtructionError` callback):"},{"id":"/root/children/100/children/0","type":"strong","loc":{"start":18394,"end":18405,"line":{"s":570,"e":570,"code":["**Runtime** (caught by `onExtructionError` callback):"]},"column":{"s":0,"e":11}},"dim":["","paragraph.100","strong.0"],"code":"**Runtime**"},{"id":"/root/children/100/children/0/children/0","type":"text","loc":{"start":18396,"end":18403,"line":{"s":570,"e":570,"code":["**Runtime** (caught by `onExtructionError` callback):"]},"column":{"s":2,"e":9}},"dim":["","paragraph.100","strong.0","text.0"],"code":"Runtime"},{"id":"/root/children/100/children/1","type":"text","loc":{"start":18405,"end":18417,"line":{"s":570,"e":570,"code":["**Runtime** (caught by `onExtructionError` callback):"]},"column":{"s":11,"e":23}},"dim":["","paragraph.100","text.1"],"code":" (caught by "},{"id":"/root/children/100/children/2","type":"inlineCode","loc":{"start":18417,"end":18436,"line":{"s":570,"e":570,"code":["**Runtime** (caught by `onExtructionError` callback):"]},"column":{"s":23,"e":42}},"dim":["","paragraph.100","inlineCode.2"],"code":"`onExtructionError`"},{"id":"/root/children/100/children/3","type":"text","loc":{"start":18436,"end":18447,"line":{"s":570,"e":570,"code":["**Runtime** (caught by `onExtructionError` callback):"]},"column":{"s":42,"e":53}},"dim":["","paragraph.100","text.3"],"code":" callback):"},{"id":"/root/children/101","type":"list","loc":{"start":18449,"end":18536,"line":{"s":572,"e":573,"code":["- Syntax errors in extruction body JS","- Runtime exceptions during extruction evaluation"]},"column":{"s":0,"e":49}},"dim":["","list.101"],"code":"- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation","symbName":"list","symbRange":[18538,21321],"symbRangeL":[572,628],"outerCode":"- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:","outerHtml":"<ul><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>"},{"id":"/root/children/101/children/0","type":"listItem","loc":{"start":18449,"end":18486,"line":{"s":572,"e":572,"code":["- Syntax errors in extruction body JS"]},"column":{"s":0,"e":37}},"dim":["","list.101","listItem.0"],"code":"- Syntax errors in extruction body JS"},{"id":"/root/children/101/children/0/children/0","type":"paragraph","loc":{"start":18451,"end":18486,"line":{"s":572,"e":572,"code":["- Syntax errors in extruction body JS"]},"column":{"s":2,"e":37}},"dim":["","list.101","listItem.0","paragraph.0"],"code":"Syntax errors in extruction body JS"},{"id":"/root/children/101/children/0/children/0/children/0","type":"text","loc":{"start":18451,"end":18486,"line":{"s":572,"e":572,"code":["- Syntax errors in extruction body JS"]},"column":{"s":2,"e":37}},"dim":["","list.101","listItem.0","paragraph.0","text.0"],"code":"Syntax errors in extruction body JS"},{"id":"/root/children/101/children/1","type":"listItem","loc":{"start":18487,"end":18536,"line":{"s":573,"e":573,"code":["- Runtime exceptions during extruction evaluation"]},"column":{"s":0,"e":49}},"dim":["","list.101","listItem.1"],"code":"- Runtime exceptions during extruction evaluation"},{"id":"/root/children/101/children/1/children/0","type":"paragraph","loc":{"start":18489,"end":18536,"line":{"s":573,"e":573,"code":["- Runtime exceptions during extruction evaluation"]},"column":{"s":2,"e":49}},"dim":["","list.101","listItem.1","paragraph.0"],"code":"Runtime exceptions during extruction evaluation"},{"id":"/root/children/101/children/1/children/0/children/0","type":"text","loc":{"start":18489,"end":18536,"line":{"s":573,"e":573,"code":["- Runtime exceptions during extruction evaluation"]},"column":{"s":2,"e":49}},"dim":["","list.101","listItem.1","paragraph.0","text.0"],"code":"Runtime exceptions during extruction evaluation"},{"id":"/root/children/102","type":"paragraph","loc":{"start":18538,"end":18648,"line":{"s":575,"e":576,"code":["When an extruction body throws during evaluation, the behavior depends on the presence","of `onExtructionError`:"]},"column":{"s":0,"e":23}},"dim":["","paragraph.102"],"code":"When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:"},{"id":"/root/children/102/children/0","type":"text","loc":{"start":18538,"end":18628,"line":{"s":575,"e":576,"code":["When an extruction body throws during evaluation, the behavior depends on the presence","of `onExtructionError`:"]},"column":{"s":0,"e":3}},"dim":["","paragraph.102","text.0"],"code":"When an extruction body throws during evaluation, the behavior depends on the presence\nof "},{"id":"/root/children/102/children/1","type":"inlineCode","loc":{"start":18628,"end":18647,"line":{"s":576,"e":576,"code":["of `onExtructionError`:"]},"column":{"s":3,"e":22}},"dim":["","paragraph.102","inlineCode.1"],"code":"`onExtructionError`"},{"id":"/root/children/102/children/2","type":"text","loc":{"start":18647,"end":18648,"line":{"s":576,"e":576,"code":["of `onExtructionError`:"]},"column":{"s":22,"e":23}},"dim":["","paragraph.102","text.2"],"code":":"},{"id":"/root/children/103","type":"paragraph","loc":{"start":18650,"end":19445,"line":{"s":578,"e":581,"code":["| Callback                          | Behavior                                                                                                                                                       |","| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |","| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |","| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |"]},"column":{"s":0,"e":198}},"dim":["","paragraph.103"],"code":"| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |"},{"id":"/root/children/103/children/0","type":"text","loc":{"start":18650,"end":19050,"line":{"s":578,"e":580,"code":["| Callback                          | Behavior                                                                                                                                                       |","| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |","| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.103","text.0"],"code":"| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| "},{"id":"/root/children/103/children/1","type":"strong","loc":{"start":19050,"end":19062,"line":{"s":580,"e":580,"code":["| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |"]},"column":{"s":2,"e":14}},"dim":["","paragraph.103","strong.1"],"code":"**Provided**"},{"id":"/root/children/103/children/1/children/0","type":"text","loc":{"start":19052,"end":19060,"line":{"s":580,"e":580,"code":["| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |"]},"column":{"s":4,"e":12}},"dim":["","paragraph.103","strong.1","text.0"],"code":"Provided"},{"id":"/root/children/103/children/2","type":"text","loc":{"start":19062,"end":19105,"line":{"s":580,"e":580,"code":["| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |"]},"column":{"s":14,"e":57}},"dim":["","paragraph.103","text.2"],"code":"                      | Error is passed to "},{"id":"/root/children/103/children/3","type":"inlineCode","loc":{"start":19105,"end":19142,"line":{"s":580,"e":580,"code":["| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |"]},"column":{"s":57,"e":94}},"dim":["","paragraph.103","inlineCode.3"],"code":"`onExtructionError(err, headingNode)`"},{"id":"/root/children/103/children/4","type":"text","loc":{"start":19142,"end":19173,"line":{"s":580,"e":580,"code":["| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |"]},"column":{"s":94,"e":125}},"dim":["","paragraph.103","text.4"],"code":"; the extruction is treated as "},{"id":"/root/children/103/children/5","type":"strong","loc":{"start":19173,"end":19188,"line":{"s":580,"e":580,"code":["| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |"]},"column":{"s":125,"e":140}},"dim":["","paragraph.103","strong.5"],"code":"**transparent**"},{"id":"/root/children/103/children/5/children/0","type":"text","loc":{"start":19175,"end":19186,"line":{"s":580,"e":580,"code":["| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |"]},"column":{"s":127,"e":138}},"dim":["","paragraph.103","strong.5","text.0"],"code":"transparent"},{"id":"/root/children/103/children/6","type":"text","loc":{"start":19188,"end":19249,"line":{"s":580,"e":581,"code":["| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |","| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |"]},"column":{"s":140,"e":2}},"dim":["","paragraph.103","text.6"],"code":" (body skipped, children promoted). Iteration continues. |\n| "},{"id":"/root/children/103/children/7","type":"strong","loc":{"start":19249,"end":19265,"line":{"s":581,"e":581,"code":["| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |"]},"column":{"s":2,"e":18}},"dim":["","paragraph.103","strong.7"],"code":"**Not provided**"},{"id":"/root/children/103/children/7/children/0","type":"text","loc":{"start":19251,"end":19263,"line":{"s":581,"e":581,"code":["| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |"]},"column":{"s":4,"e":16}},"dim":["","paragraph.103","strong.7","text.0"],"code":"Not provided"},{"id":"/root/children/103/children/8","type":"text","loc":{"start":19265,"end":19267,"line":{"s":581,"e":581,"code":["| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |"]},"column":{"s":18,"e":20}},"dim":["","paragraph.103","text.8"],"code":" ("},{"id":"/root/children/103/children/9","type":"inlineCode","loc":{"start":19267,"end":19273,"line":{"s":581,"e":581,"code":["| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |"]},"column":{"s":20,"e":26}},"dim":["","paragraph.103","inlineCode.9"],"code":"`null`"},{"id":"/root/children/103/children/10","type":"text","loc":{"start":19273,"end":19291,"line":{"s":581,"e":581,"code":["| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |"]},"column":{"s":26,"e":44}},"dim":["","paragraph.103","text.10"],"code":"/omitted) | Error "},{"id":"/root/children/103/children/11","type":"strong","loc":{"start":19291,"end":19305,"line":{"s":581,"e":581,"code":["| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |"]},"column":{"s":44,"e":58}},"dim":["","paragraph.103","strong.11"],"code":"**propagates**"},{"id":"/root/children/103/children/11/children/0","type":"text","loc":{"start":19293,"end":19303,"line":{"s":581,"e":581,"code":["| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |"]},"column":{"s":46,"e":56}},"dim":["","paragraph.103","strong.11","text.0"],"code":"propagates"},{"id":"/root/children/103/children/12","type":"text","loc":{"start":19305,"end":19324,"line":{"s":581,"e":581,"code":["| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |"]},"column":{"s":58,"e":77}},"dim":["","paragraph.103","text.12"],"code":" to the consumer's "},{"id":"/root/children/103/children/13","type":"inlineCode","loc":{"start":19324,"end":19335,"line":{"s":581,"e":581,"code":["| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |"]},"column":{"s":77,"e":88}},"dim":["","paragraph.103","inlineCode.13"],"code":"`for await`"},{"id":"/root/children/103/children/14","type":"text","loc":{"start":19335,"end":19445,"line":{"s":581,"e":581,"code":["| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |"]},"column":{"s":88,"e":198}},"dim":["","paragraph.103","text.14"],"code":" loop (backward compatible).                                                                                 |"},{"id":"/root/children/104","type":"paragraph","loc":{"start":19447,"end":19607,"line":{"s":583,"e":584,"code":["In `children` resolution, an errored child extruction follows the same rule — treated","as transparent, its children promoted into the parent's `children` output."]},"column":{"s":0,"e":74}},"dim":["","paragraph.104"],"code":"In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output."},{"id":"/root/children/104/children/0","type":"text","loc":{"start":19447,"end":19450,"line":{"s":583,"e":583,"code":["In `children` resolution, an errored child extruction follows the same rule — treated"]},"column":{"s":0,"e":3}},"dim":["","paragraph.104","text.0"],"code":"In "},{"id":"/root/children/104/children/1","type":"inlineCode","loc":{"start":19450,"end":19460,"line":{"s":583,"e":583,"code":["In `children` resolution, an errored child extruction follows the same rule — treated"]},"column":{"s":3,"e":13}},"dim":["","paragraph.104","inlineCode.1"],"code":"`children`"},{"id":"/root/children/104/children/2","type":"text","loc":{"start":19460,"end":19589,"line":{"s":583,"e":584,"code":["In `children` resolution, an errored child extruction follows the same rule — treated","as transparent, its children promoted into the parent's `children` output."]},"column":{"s":13,"e":56}},"dim":["","paragraph.104","text.2"],"code":" resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's "},{"id":"/root/children/104/children/3","type":"inlineCode","loc":{"start":19589,"end":19599,"line":{"s":584,"e":584,"code":["as transparent, its children promoted into the parent's `children` output."]},"column":{"s":56,"e":66}},"dim":["","paragraph.104","inlineCode.3"],"code":"`children`"},{"id":"/root/children/104/children/4","type":"text","loc":{"start":19599,"end":19607,"line":{"s":584,"e":584,"code":["as transparent, its children promoted into the parent's `children` output."]},"column":{"s":66,"e":74}},"dim":["","paragraph.104","text.4"],"code":" output."},{"id":"/root/children/105","type":"paragraph","loc":{"start":19609,"end":19680,"line":{"s":586,"e":586,"code":["All errors include the source position (`node.position`) for debugging."]},"column":{"s":0,"e":71}},"dim":["","paragraph.105"],"code":"All errors include the source position (`node.position`) for debugging."},{"id":"/root/children/105/children/0","type":"text","loc":{"start":19609,"end":19649,"line":{"s":586,"e":586,"code":["All errors include the source position (`node.position`) for debugging."]},"column":{"s":0,"e":40}},"dim":["","paragraph.105","text.0"],"code":"All errors include the source position ("},{"id":"/root/children/105/children/1","type":"inlineCode","loc":{"start":19649,"end":19664,"line":{"s":586,"e":586,"code":["All errors include the source position (`node.position`) for debugging."]},"column":{"s":40,"e":55}},"dim":["","paragraph.105","inlineCode.1"],"code":"`node.position`"},{"id":"/root/children/105/children/2","type":"text","loc":{"start":19664,"end":19680,"line":{"s":586,"e":586,"code":["All errors include the source position (`node.position`) for debugging."]},"column":{"s":55,"e":71}},"dim":["","paragraph.105","text.2"],"code":") for debugging."},{"id":"/root/children/106","type":"heading","loc":{"start":19682,"end":19699,"line":{"s":588,"e":588,"code":["## Open Questions"]},"column":{"s":0,"e":17}},"dim":["","heading.106"],"code":"## Open Questions","symbName":"heading","symbRange":[19701,58640],"symbRangeL":[588,589],"outerCode":"","outerHtml":""},{"id":"/root/children/106/children/0","type":"text","loc":{"start":19685,"end":19699,"line":{"s":588,"e":588,"code":["## Open Questions"]},"column":{"s":3,"e":17}},"dim":["","heading.106","text.0"],"code":"Open Questions"},{"id":"/root/children/107","type":"heading","loc":{"start":19701,"end":19730,"line":{"s":590,"e":590,"code":["### 1. What is `context` for?"]},"column":{"s":0,"e":29}},"dim":["","heading.107"],"code":"### 1. What is `context` for?","symbName":"heading","symbRange":[19732,20105],"symbRangeL":[590,600],"outerCode":"\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.","outerHtml":"\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>"},{"id":"/root/children/107/children/0","type":"text","loc":{"start":19705,"end":19716,"line":{"s":590,"e":590,"code":["### 1. What is `context` for?"]},"column":{"s":4,"e":15}},"dim":["","heading.107","text.0"],"code":"1. What is "},{"id":"/root/children/107/children/1","type":"inlineCode","loc":{"start":19716,"end":19725,"line":{"s":590,"e":590,"code":["### 1. What is `context` for?"]},"column":{"s":15,"e":24}},"dim":["","heading.107","inlineCode.1"],"code":"`context`"},{"id":"/root/children/107/children/2","type":"text","loc":{"start":19725,"end":19730,"line":{"s":590,"e":590,"code":["### 1. What is `context` for?"]},"column":{"s":24,"e":29}},"dim":["","heading.107","text.2"],"code":" for?"},{"id":"/root/children/108","type":"paragraph","loc":{"start":19732,"end":19959,"line":{"s":592,"e":595,"code":["**Resolved:** `context` is **state** — a bag of global variables","that the document can reference.","With `evalFn`, extruction bodies can access context keys as named","parameters. Without `evalFn`, `context` is accepted but unused."]},"column":{"s":0,"e":63}},"dim":["","paragraph.108"],"code":"**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused."},{"id":"/root/children/108/children/0","type":"strong","loc":{"start":19732,"end":19745,"line":{"s":592,"e":592,"code":["**Resolved:** `context` is **state** — a bag of global variables"]},"column":{"s":0,"e":13}},"dim":["","paragraph.108","strong.0"],"code":"**Resolved:**"},{"id":"/root/children/108/children/0/children/0","type":"text","loc":{"start":19734,"end":19743,"line":{"s":592,"e":592,"code":["**Resolved:** `context` is **state** — a bag of global variables"]},"column":{"s":2,"e":11}},"dim":["","paragraph.108","strong.0","text.0"],"code":"Resolved:"},{"id":"/root/children/108/children/1","type":"text","loc":{"start":19745,"end":19746,"line":{"s":592,"e":592,"code":["**Resolved:** `context` is **state** — a bag of global variables"]},"column":{"s":13,"e":14}},"dim":["","paragraph.108","text.1"],"code":" "},{"id":"/root/children/108/children/2","type":"inlineCode","loc":{"start":19746,"end":19755,"line":{"s":592,"e":592,"code":["**Resolved:** `context` is **state** — a bag of global variables"]},"column":{"s":14,"e":23}},"dim":["","paragraph.108","inlineCode.2"],"code":"`context`"},{"id":"/root/children/108/children/3","type":"text","loc":{"start":19755,"end":19759,"line":{"s":592,"e":592,"code":["**Resolved:** `context` is **state** — a bag of global variables"]},"column":{"s":23,"e":27}},"dim":["","paragraph.108","text.3"],"code":" is "},{"id":"/root/children/108/children/4","type":"strong","loc":{"start":19759,"end":19768,"line":{"s":592,"e":592,"code":["**Resolved:** `context` is **state** — a bag of global variables"]},"column":{"s":27,"e":36}},"dim":["","paragraph.108","strong.4"],"code":"**state**"},{"id":"/root/children/108/children/4/children/0","type":"text","loc":{"start":19761,"end":19766,"line":{"s":592,"e":592,"code":["**Resolved:** `context` is **state** — a bag of global variables"]},"column":{"s":29,"e":34}},"dim":["","paragraph.108","strong.4","text.0"],"code":"state"},{"id":"/root/children/108/children/5","type":"text","loc":{"start":19768,"end":19835,"line":{"s":592,"e":594,"code":["**Resolved:** `context` is **state** — a bag of global variables","that the document can reference.","With `evalFn`, extruction bodies can access context keys as named"]},"column":{"s":36,"e":5}},"dim":["","paragraph.108","text.5"],"code":" — a bag of global variables\nthat the document can reference.\nWith "},{"id":"/root/children/108/children/6","type":"inlineCode","loc":{"start":19835,"end":19843,"line":{"s":594,"e":594,"code":["With `evalFn`, extruction bodies can access context keys as named"]},"column":{"s":5,"e":13}},"dim":["","paragraph.108","inlineCode.6"],"code":"`evalFn`"},{"id":"/root/children/108/children/7","type":"text","loc":{"start":19843,"end":19916,"line":{"s":594,"e":595,"code":["With `evalFn`, extruction bodies can access context keys as named","parameters. Without `evalFn`, `context` is accepted but unused."]},"column":{"s":13,"e":20}},"dim":["","paragraph.108","text.7"],"code":", extruction bodies can access context keys as named\nparameters. Without "},{"id":"/root/children/108/children/8","type":"inlineCode","loc":{"start":19916,"end":19924,"line":{"s":595,"e":595,"code":["parameters. Without `evalFn`, `context` is accepted but unused."]},"column":{"s":20,"e":28}},"dim":["","paragraph.108","inlineCode.8"],"code":"`evalFn`"},{"id":"/root/children/108/children/9","type":"text","loc":{"start":19924,"end":19926,"line":{"s":595,"e":595,"code":["parameters. Without `evalFn`, `context` is accepted but unused."]},"column":{"s":28,"e":30}},"dim":["","paragraph.108","text.9"],"code":", "},{"id":"/root/children/108/children/10","type":"inlineCode","loc":{"start":19926,"end":19935,"line":{"s":595,"e":595,"code":["parameters. Without `evalFn`, `context` is accepted but unused."]},"column":{"s":30,"e":39}},"dim":["","paragraph.108","inlineCode.10"],"code":"`context`"},{"id":"/root/children/108/children/11","type":"text","loc":{"start":19935,"end":19959,"line":{"s":595,"e":595,"code":["parameters. Without `evalFn`, `context` is accepted but unused."]},"column":{"s":39,"e":63}},"dim":["","paragraph.108","text.11"],"code":" is accepted but unused."},{"id":"/root/children/109","type":"paragraph","loc":{"start":19961,"end":20105,"line":{"s":597,"e":599,"code":["The runner signature stays `runner(context, opts?)`.","With no active extructions, `context` is accepted but unused — a","forward-looking parameter."]},"column":{"s":0,"e":26}},"dim":["","paragraph.109"],"code":"The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter."},{"id":"/root/children/109/children/0","type":"text","loc":{"start":19961,"end":19988,"line":{"s":597,"e":597,"code":["The runner signature stays `runner(context, opts?)`."]},"column":{"s":0,"e":27}},"dim":["","paragraph.109","text.0"],"code":"The runner signature stays "},{"id":"/root/children/109/children/1","type":"inlineCode","loc":{"start":19988,"end":20012,"line":{"s":597,"e":597,"code":["The runner signature stays `runner(context, opts?)`."]},"column":{"s":27,"e":51}},"dim":["","paragraph.109","inlineCode.1"],"code":"`runner(context, opts?)`"},{"id":"/root/children/109/children/2","type":"text","loc":{"start":20012,"end":20042,"line":{"s":597,"e":598,"code":["The runner signature stays `runner(context, opts?)`.","With no active extructions, `context` is accepted but unused — a"]},"column":{"s":51,"e":28}},"dim":["","paragraph.109","text.2"],"code":".\nWith no active extructions, "},{"id":"/root/children/109/children/3","type":"inlineCode","loc":{"start":20042,"end":20051,"line":{"s":598,"e":598,"code":["With no active extructions, `context` is accepted but unused — a"]},"column":{"s":28,"e":37}},"dim":["","paragraph.109","inlineCode.3"],"code":"`context`"},{"id":"/root/children/109/children/4","type":"text","loc":{"start":20051,"end":20105,"line":{"s":598,"e":599,"code":["With no active extructions, `context` is accepted but unused — a","forward-looking parameter."]},"column":{"s":37,"e":26}},"dim":["","paragraph.109","text.4"],"code":" is accepted but unused — a\nforward-looking parameter."},{"id":"/root/children/110","type":"heading","loc":{"start":20107,"end":20140,"line":{"s":601,"e":601,"code":["### 2. Extruction label semantics"]},"column":{"s":0,"e":33}},"dim":["","heading.110"],"code":"### 2. Extruction label semantics","symbName":"heading","symbRange":[20142,20333],"symbRangeL":[601,606],"outerCode":"\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.","outerHtml":"\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>"},{"id":"/root/children/110/children/0","type":"text","loc":{"start":20111,"end":20140,"line":{"s":601,"e":601,"code":["### 2. Extruction label semantics"]},"column":{"s":4,"e":33}},"dim":["","heading.110","text.0"],"code":"2. Extruction label semantics"},{"id":"/root/children/111","type":"paragraph","loc":{"start":20142,"end":20333,"line":{"s":603,"e":605,"code":["**Deferred.** `data.label` is a free-form string — the text between `${}`.","Its semantics are intentionally undefined until extruction evaluation","is designed. Currently just stored, no effect."]},"column":{"s":0,"e":46}},"dim":["","paragraph.111"],"code":"**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect."},{"id":"/root/children/111/children/0","type":"strong","loc":{"start":20142,"end":20155,"line":{"s":603,"e":603,"code":["**Deferred.** `data.label` is a free-form string — the text between `${}`."]},"column":{"s":0,"e":13}},"dim":["","paragraph.111","strong.0"],"code":"**Deferred.**"},{"id":"/root/children/111/children/0/children/0","type":"text","loc":{"start":20144,"end":20153,"line":{"s":603,"e":603,"code":["**Deferred.** `data.label` is a free-form string — the text between `${}`."]},"column":{"s":2,"e":11}},"dim":["","paragraph.111","strong.0","text.0"],"code":"Deferred."},{"id":"/root/children/111/children/1","type":"text","loc":{"start":20155,"end":20156,"line":{"s":603,"e":603,"code":["**Deferred.** `data.label` is a free-form string — the text between `${}`."]},"column":{"s":13,"e":14}},"dim":["","paragraph.111","text.1"],"code":" "},{"id":"/root/children/111/children/2","type":"inlineCode","loc":{"start":20156,"end":20168,"line":{"s":603,"e":603,"code":["**Deferred.** `data.label` is a free-form string — the text between `${}`."]},"column":{"s":14,"e":26}},"dim":["","paragraph.111","inlineCode.2"],"code":"`data.label`"},{"id":"/root/children/111/children/3","type":"text","loc":{"start":20168,"end":20210,"line":{"s":603,"e":603,"code":["**Deferred.** `data.label` is a free-form string — the text between `${}`."]},"column":{"s":26,"e":68}},"dim":["","paragraph.111","text.3"],"code":" is a free-form string — the text between "},{"id":"/root/children/111/children/4","type":"inlineCode","loc":{"start":20210,"end":20215,"line":{"s":603,"e":603,"code":["**Deferred.** `data.label` is a free-form string — the text between `${}`."]},"column":{"s":68,"e":73}},"dim":["","paragraph.111","inlineCode.4"],"code":"`${}`"},{"id":"/root/children/111/children/5","type":"text","loc":{"start":20215,"end":20333,"line":{"s":603,"e":605,"code":["**Deferred.** `data.label` is a free-form string — the text between `${}`.","Its semantics are intentionally undefined until extruction evaluation","is designed. Currently just stored, no effect."]},"column":{"s":73,"e":46}},"dim":["","paragraph.111","text.5"],"code":".\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect."},{"id":"/root/children/112","type":"heading","loc":{"start":20335,"end":20379,"line":{"s":607,"e":607,"code":["### 3. When will extruction bodies activate?"]},"column":{"s":0,"e":44}},"dim":["","heading.112"],"code":"### 3. When will extruction bodies activate?","symbName":"heading","symbRange":[20381,20662],"symbRangeL":[607,613],"outerCode":"\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).","outerHtml":"\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>"},{"id":"/root/children/112/children/0","type":"text","loc":{"start":20339,"end":20379,"line":{"s":607,"e":607,"code":["### 3. When will extruction bodies activate?"]},"column":{"s":4,"e":44}},"dim":["","heading.112","text.0"],"code":"3. When will extruction bodies activate?"},{"id":"/root/children/113","type":"paragraph","loc":{"start":20381,"end":20662,"line":{"s":609,"e":612,"code":["**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is","provided. Only ` ```javascript ` code blocks within the body are extracted —","non-javascript code blocks and other markdown content are ignored.","Without `evalFn`, the body remains inert (silently dropped)."]},"column":{"s":0,"e":60}},"dim":["","paragraph.113"],"code":"**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped)."},{"id":"/root/children/113/children/0","type":"strong","loc":{"start":20381,"end":20394,"line":{"s":609,"e":609,"code":["**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is"]},"column":{"s":0,"e":13}},"dim":["","paragraph.113","strong.0"],"code":"**Resolved.**"},{"id":"/root/children/113/children/0/children/0","type":"text","loc":{"start":20383,"end":20392,"line":{"s":609,"e":609,"code":["**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is"]},"column":{"s":2,"e":11}},"dim":["","paragraph.113","strong.0","text.0"],"code":"Resolved."},{"id":"/root/children/113/children/1","type":"text","loc":{"start":20394,"end":20446,"line":{"s":609,"e":609,"code":["**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is"]},"column":{"s":13,"e":65}},"dim":["","paragraph.113","text.1"],"code":" Extruction bodies are evaluated as JavaScript when "},{"id":"/root/children/113/children/2","type":"inlineCode","loc":{"start":20446,"end":20454,"line":{"s":609,"e":609,"code":["**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is"]},"column":{"s":65,"e":73}},"dim":["","paragraph.113","inlineCode.2"],"code":"`evalFn`"},{"id":"/root/children/113/children/3","type":"text","loc":{"start":20454,"end":20473,"line":{"s":609,"e":610,"code":["**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is","provided. Only ` ```javascript ` code blocks within the body are extracted —"]},"column":{"s":73,"e":15}},"dim":["","paragraph.113","text.3"],"code":" is\nprovided. Only "},{"id":"/root/children/113/children/4","type":"inlineCode","loc":{"start":20473,"end":20490,"line":{"s":610,"e":610,"code":["provided. Only ` ```javascript ` code blocks within the body are extracted —"]},"column":{"s":15,"e":32}},"dim":["","paragraph.113","inlineCode.4"],"code":"` ```javascript `"},{"id":"/root/children/113/children/5","type":"text","loc":{"start":20490,"end":20610,"line":{"s":610,"e":612,"code":["provided. Only ` ```javascript ` code blocks within the body are extracted —","non-javascript code blocks and other markdown content are ignored.","Without `evalFn`, the body remains inert (silently dropped)."]},"column":{"s":32,"e":8}},"dim":["","paragraph.113","text.5"],"code":" code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout "},{"id":"/root/children/113/children/6","type":"inlineCode","loc":{"start":20610,"end":20618,"line":{"s":612,"e":612,"code":["Without `evalFn`, the body remains inert (silently dropped)."]},"column":{"s":8,"e":16}},"dim":["","paragraph.113","inlineCode.6"],"code":"`evalFn`"},{"id":"/root/children/113/children/7","type":"text","loc":{"start":20618,"end":20662,"line":{"s":612,"e":612,"code":["Without `evalFn`, the body remains inert (silently dropped)."]},"column":{"s":16,"e":60}},"dim":["","paragraph.113","text.7"],"code":", the body remains inert (silently dropped)."},{"id":"/root/children/114","type":"heading","loc":{"start":20664,"end":20701,"line":{"s":614,"e":614,"code":["### 4. Verbatim vs canonicalized body"]},"column":{"s":0,"e":37}},"dim":["","heading.114"],"code":"### 4. Verbatim vs canonicalized body","symbName":"heading","symbRange":[20703,21051],"symbRangeL":[614,621],"outerCode":"\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.","outerHtml":"\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>"},{"id":"/root/children/114/children/0","type":"text","loc":{"start":20668,"end":20701,"line":{"s":614,"e":614,"code":["### 4. Verbatim vs canonicalized body"]},"column":{"s":4,"e":37}},"dim":["","heading.114","text.0"],"code":"4. Verbatim vs canonicalized body"},{"id":"/root/children/115","type":"paragraph","loc":{"start":20703,"end":21051,"line":{"s":616,"e":620,"code":["**Resolved.** `body` is canonicalized by default (re-stringified remark","nodes). Source position (`node.position`) is the escape hatch for","verbatim access. No default flip — canonicalized is the correct default","because consumers should get consistent, predictable markdown output.","If verbatim is needed, slice the original text using source offsets."]},"column":{"s":0,"e":68}},"dim":["","paragraph.115"],"code":"**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets."},{"id":"/root/children/115/children/0","type":"strong","loc":{"start":20703,"end":20716,"line":{"s":616,"e":616,"code":["**Resolved.** `body` is canonicalized by default (re-stringified remark"]},"column":{"s":0,"e":13}},"dim":["","paragraph.115","strong.0"],"code":"**Resolved.**"},{"id":"/root/children/115/children/0/children/0","type":"text","loc":{"start":20705,"end":20714,"line":{"s":616,"e":616,"code":["**Resolved.** `body` is canonicalized by default (re-stringified remark"]},"column":{"s":2,"e":11}},"dim":["","paragraph.115","strong.0","text.0"],"code":"Resolved."},{"id":"/root/children/115/children/1","type":"text","loc":{"start":20716,"end":20717,"line":{"s":616,"e":616,"code":["**Resolved.** `body` is canonicalized by default (re-stringified remark"]},"column":{"s":13,"e":14}},"dim":["","paragraph.115","text.1"],"code":" "},{"id":"/root/children/115/children/2","type":"inlineCode","loc":{"start":20717,"end":20723,"line":{"s":616,"e":616,"code":["**Resolved.** `body` is canonicalized by default (re-stringified remark"]},"column":{"s":14,"e":20}},"dim":["","paragraph.115","inlineCode.2"],"code":"`body`"},{"id":"/root/children/115/children/3","type":"text","loc":{"start":20723,"end":20800,"line":{"s":616,"e":617,"code":["**Resolved.** `body` is canonicalized by default (re-stringified remark","nodes). Source position (`node.position`) is the escape hatch for"]},"column":{"s":20,"e":25}},"dim":["","paragraph.115","text.3"],"code":" is canonicalized by default (re-stringified remark\nnodes). Source position ("},{"id":"/root/children/115/children/4","type":"inlineCode","loc":{"start":20800,"end":20815,"line":{"s":617,"e":617,"code":["nodes). Source position (`node.position`) is the escape hatch for"]},"column":{"s":25,"e":40}},"dim":["","paragraph.115","inlineCode.4"],"code":"`node.position`"},{"id":"/root/children/115/children/5","type":"text","loc":{"start":20815,"end":21051,"line":{"s":617,"e":620,"code":["nodes). Source position (`node.position`) is the escape hatch for","verbatim access. No default flip — canonicalized is the correct default","because consumers should get consistent, predictable markdown output.","If verbatim is needed, slice the original text using source offsets."]},"column":{"s":40,"e":68}},"dim":["","paragraph.115","text.5"],"code":") is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets."},{"id":"/root/children/116","type":"heading","loc":{"start":21053,"end":21089,"line":{"s":622,"e":622,"code":["### 5. `hasChildren` and extructions"]},"column":{"s":0,"e":36}},"dim":["","heading.116"],"code":"### 5. `hasChildren` and extructions","symbName":"heading","symbRange":[21091,22251],"symbRangeL":[622,645],"outerCode":"\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.","outerHtml":"\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>"},{"id":"/root/children/116/children/0","type":"text","loc":{"start":21057,"end":21060,"line":{"s":622,"e":622,"code":["### 5. `hasChildren` and extructions"]},"column":{"s":4,"e":7}},"dim":["","heading.116","text.0"],"code":"5. "},{"id":"/root/children/116/children/1","type":"inlineCode","loc":{"start":21060,"end":21073,"line":{"s":622,"e":622,"code":["### 5. `hasChildren` and extructions"]},"column":{"s":7,"e":20}},"dim":["","heading.116","inlineCode.1"],"code":"`hasChildren`"},{"id":"/root/children/116/children/2","type":"text","loc":{"start":21073,"end":21089,"line":{"s":622,"e":622,"code":["### 5. `hasChildren` and extructions"]},"column":{"s":20,"e":36}},"dim":["","heading.116","text.2"],"code":" and extructions"},{"id":"/root/children/117","type":"paragraph","loc":{"start":21091,"end":21321,"line":{"s":624,"e":627,"code":["**Resolved — extructions are fully transparent with child promotion.**","Extructions are skipped from both output and navigation. Non-extruction","headings nested under an extruction are **promoted** to the parent's","`expand()` output:"]},"column":{"s":0,"e":18}},"dim":["","paragraph.117"],"code":"**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:"},{"id":"/root/children/117/children/0","type":"strong","loc":{"start":21091,"end":21161,"line":{"s":624,"e":624,"code":["**Resolved — extructions are fully transparent with child promotion.**"]},"column":{"s":0,"e":70}},"dim":["","paragraph.117","strong.0"],"code":"**Resolved — extructions are fully transparent with child promotion.**"},{"id":"/root/children/117/children/0/children/0","type":"text","loc":{"start":21093,"end":21159,"line":{"s":624,"e":624,"code":["**Resolved — extructions are fully transparent with child promotion.**"]},"column":{"s":2,"e":68}},"dim":["","paragraph.117","strong.0","text.0"],"code":"Resolved — extructions are fully transparent with child promotion."},{"id":"/root/children/117/children/1","type":"text","loc":{"start":21161,"end":21274,"line":{"s":624,"e":626,"code":["**Resolved — extructions are fully transparent with child promotion.**","Extructions are skipped from both output and navigation. Non-extruction","headings nested under an extruction are **promoted** to the parent's"]},"column":{"s":70,"e":40}},"dim":["","paragraph.117","text.1"],"code":"\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are "},{"id":"/root/children/117/children/2","type":"strong","loc":{"start":21274,"end":21286,"line":{"s":626,"e":626,"code":["headings nested under an extruction are **promoted** to the parent's"]},"column":{"s":40,"e":52}},"dim":["","paragraph.117","strong.2"],"code":"**promoted**"},{"id":"/root/children/117/children/2/children/0","type":"text","loc":{"start":21276,"end":21284,"line":{"s":626,"e":626,"code":["headings nested under an extruction are **promoted** to the parent's"]},"column":{"s":42,"e":50}},"dim":["","paragraph.117","strong.2","text.0"],"code":"promoted"},{"id":"/root/children/117/children/3","type":"text","loc":{"start":21286,"end":21303,"line":{"s":626,"e":627,"code":["headings nested under an extruction are **promoted** to the parent's","`expand()` output:"]},"column":{"s":52,"e":0}},"dim":["","paragraph.117","text.3"],"code":" to the parent's\n"},{"id":"/root/children/117/children/4","type":"inlineCode","loc":{"start":21303,"end":21313,"line":{"s":627,"e":627,"code":["`expand()` output:"]},"column":{"s":0,"e":10}},"dim":["","paragraph.117","inlineCode.4"],"code":"`expand()`"},{"id":"/root/children/117/children/5","type":"text","loc":{"start":21313,"end":21321,"line":{"s":627,"e":627,"code":["`expand()` output:"]},"column":{"s":10,"e":18}},"dim":["","paragraph.117","text.5"],"code":" output:"},{"id":"/root/children/118","type":"list","loc":{"start":21323,"end":22251,"line":{"s":629,"e":644,"code":["- `hasChildren` reports what `expand()` would yield — this includes","  promoted children under extructions.","- Child headings nested under an extruction get their trail computed","  as if the extruction doesn't exist — they attach to the nearest","  non-extruction ancestor heading.","- Extruction body content is still silently dropped; only the promoted","  heading (and its own subtree) survives.","- `skipExtructionBody()` is the shared helper that implements this:","  given an extruction heading index, it advances past non-heading body","  content but returns at the first heading (promoted child) rather than","  skipping the entire subtree.","- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,","  and `findInHeadings` all agree on which headings are reachable.","- Rationale: extructions are inert markers by default; their body is","  dropped (or evaluated with `evalFn`), but document structure under","  them is preserved."]},"column":{"s":0,"e":20}},"dim":["","list.118"],"code":"- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.","symbName":"list","symbRange":[22253,22411],"symbRangeL":[629,650],"outerCode":"  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:","outerHtml":"<p>  promoted children under extructions.</p><ul><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>"},{"id":"/root/children/118/children/0","type":"listItem","loc":{"start":21323,"end":21429,"line":{"s":629,"e":630,"code":["- `hasChildren` reports what `expand()` would yield — this includes","  promoted children under extructions."]},"column":{"s":0,"e":38}},"dim":["","list.118","listItem.0"],"code":"- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions."},{"id":"/root/children/118/children/0/children/0","type":"paragraph","loc":{"start":21325,"end":21429,"line":{"s":629,"e":630,"code":["- `hasChildren` reports what `expand()` would yield — this includes","  promoted children under extructions."]},"column":{"s":2,"e":38}},"dim":["","list.118","listItem.0","paragraph.0"],"code":"`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions."},{"id":"/root/children/118/children/0/children/0/children/0","type":"inlineCode","loc":{"start":21325,"end":21338,"line":{"s":629,"e":629,"code":["- `hasChildren` reports what `expand()` would yield — this includes"]},"column":{"s":2,"e":15}},"dim":["","list.118","listItem.0","paragraph.0","inlineCode.0"],"code":"`hasChildren`"},{"id":"/root/children/118/children/0/children/0/children/1","type":"text","loc":{"start":21338,"end":21352,"line":{"s":629,"e":629,"code":["- `hasChildren` reports what `expand()` would yield — this includes"]},"column":{"s":15,"e":29}},"dim":["","list.118","listItem.0","paragraph.0","text.1"],"code":" reports what "},{"id":"/root/children/118/children/0/children/0/children/2","type":"inlineCode","loc":{"start":21352,"end":21362,"line":{"s":629,"e":629,"code":["- `hasChildren` reports what `expand()` would yield — this includes"]},"column":{"s":29,"e":39}},"dim":["","list.118","listItem.0","paragraph.0","inlineCode.2"],"code":"`expand()`"},{"id":"/root/children/118/children/0/children/0/children/3","type":"text","loc":{"start":21362,"end":21429,"line":{"s":629,"e":630,"code":["- `hasChildren` reports what `expand()` would yield — this includes","  promoted children under extructions."]},"column":{"s":39,"e":38}},"dim":["","list.118","listItem.0","paragraph.0","text.3"],"code":" would yield — this includes\n  promoted children under extructions."},{"id":"/root/children/118/children/1","type":"listItem","loc":{"start":21430,"end":21599,"line":{"s":631,"e":633,"code":["- Child headings nested under an extruction get their trail computed","  as if the extruction doesn't exist — they attach to the nearest","  non-extruction ancestor heading."]},"column":{"s":0,"e":34}},"dim":["","list.118","listItem.1"],"code":"- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading."},{"id":"/root/children/118/children/1/children/0","type":"paragraph","loc":{"start":21432,"end":21599,"line":{"s":631,"e":633,"code":["- Child headings nested under an extruction get their trail computed","  as if the extruction doesn't exist — they attach to the nearest","  non-extruction ancestor heading."]},"column":{"s":2,"e":34}},"dim":["","list.118","listItem.1","paragraph.0"],"code":"Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading."},{"id":"/root/children/118/children/1/children/0/children/0","type":"text","loc":{"start":21432,"end":21599,"line":{"s":631,"e":633,"code":["- Child headings nested under an extruction get their trail computed","  as if the extruction doesn't exist — they attach to the nearest","  non-extruction ancestor heading."]},"column":{"s":2,"e":34}},"dim":["","list.118","listItem.1","paragraph.0","text.0"],"code":"Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading."},{"id":"/root/children/118/children/2","type":"listItem","loc":{"start":21600,"end":21712,"line":{"s":634,"e":635,"code":["- Extruction body content is still silently dropped; only the promoted","  heading (and its own subtree) survives."]},"column":{"s":0,"e":41}},"dim":["","list.118","listItem.2"],"code":"- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives."},{"id":"/root/children/118/children/2/children/0","type":"paragraph","loc":{"start":21602,"end":21712,"line":{"s":634,"e":635,"code":["- Extruction body content is still silently dropped; only the promoted","  heading (and its own subtree) survives."]},"column":{"s":2,"e":41}},"dim":["","list.118","listItem.2","paragraph.0"],"code":"Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives."},{"id":"/root/children/118/children/2/children/0/children/0","type":"text","loc":{"start":21602,"end":21712,"line":{"s":634,"e":635,"code":["- Extruction body content is still silently dropped; only the promoted","  heading (and its own subtree) survives."]},"column":{"s":2,"e":41}},"dim":["","list.118","listItem.2","paragraph.0","text.0"],"code":"Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives."},{"id":"/root/children/118/children/3","type":"listItem","loc":{"start":21713,"end":21954,"line":{"s":636,"e":639,"code":["- `skipExtructionBody()` is the shared helper that implements this:","  given an extruction heading index, it advances past non-heading body","  content but returns at the first heading (promoted child) rather than","  skipping the entire subtree."]},"column":{"s":0,"e":30}},"dim":["","list.118","listItem.3"],"code":"- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree."},{"id":"/root/children/118/children/3/children/0","type":"paragraph","loc":{"start":21715,"end":21954,"line":{"s":636,"e":639,"code":["- `skipExtructionBody()` is the shared helper that implements this:","  given an extruction heading index, it advances past non-heading body","  content but returns at the first heading (promoted child) rather than","  skipping the entire subtree."]},"column":{"s":2,"e":30}},"dim":["","list.118","listItem.3","paragraph.0"],"code":"`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree."},{"id":"/root/children/118/children/3/children/0/children/0","type":"inlineCode","loc":{"start":21715,"end":21737,"line":{"s":636,"e":636,"code":["- `skipExtructionBody()` is the shared helper that implements this:"]},"column":{"s":2,"e":24}},"dim":["","list.118","listItem.3","paragraph.0","inlineCode.0"],"code":"`skipExtructionBody()`"},{"id":"/root/children/118/children/3/children/0/children/1","type":"text","loc":{"start":21737,"end":21954,"line":{"s":636,"e":639,"code":["- `skipExtructionBody()` is the shared helper that implements this:","  given an extruction heading index, it advances past non-heading body","  content but returns at the first heading (promoted child) rather than","  skipping the entire subtree."]},"column":{"s":24,"e":30}},"dim":["","list.118","listItem.3","paragraph.0","text.1"],"code":" is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree."},{"id":"/root/children/118/children/4","type":"listItem","loc":{"start":21955,"end":22092,"line":{"s":640,"e":641,"code":["- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,","  and `findInHeadings` all agree on which headings are reachable."]},"column":{"s":0,"e":65}},"dim":["","list.118","listItem.4"],"code":"- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable."},{"id":"/root/children/118/children/4/children/0","type":"paragraph","loc":{"start":21957,"end":22092,"line":{"s":640,"e":641,"code":["- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,","  and `findInHeadings` all agree on which headings are reachable."]},"column":{"s":2,"e":65}},"dim":["","list.118","listItem.4","paragraph.0"],"code":"Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable."},{"id":"/root/children/118/children/4/children/0/children/0","type":"text","loc":{"start":21957,"end":21980,"line":{"s":640,"e":640,"code":["- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,"]},"column":{"s":2,"e":25}},"dim":["","list.118","listItem.4","paragraph.0","text.0"],"code":"Consistency invariant: "},{"id":"/root/children/118/children/4/children/0/children/1","type":"inlineCode","loc":{"start":21980,"end":21990,"line":{"s":640,"e":640,"code":["- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,"]},"column":{"s":25,"e":35}},"dim":["","list.118","listItem.4","paragraph.0","inlineCode.1"],"code":"`expand()`"},{"id":"/root/children/118/children/4/children/0/children/2","type":"text","loc":{"start":21990,"end":21992,"line":{"s":640,"e":640,"code":["- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,"]},"column":{"s":35,"e":37}},"dim":["","list.118","listItem.4","paragraph.0","text.2"],"code":", "},{"id":"/root/children/118/children/4/children/0/children/3","type":"inlineCode","loc":{"start":21992,"end":22005,"line":{"s":640,"e":640,"code":["- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,"]},"column":{"s":37,"e":50}},"dim":["","list.118","listItem.4","paragraph.0","inlineCode.3"],"code":"`hasChildren`"},{"id":"/root/children/118/children/4/children/0/children/4","type":"text","loc":{"start":22005,"end":22007,"line":{"s":640,"e":640,"code":["- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,"]},"column":{"s":50,"e":52}},"dim":["","list.118","listItem.4","paragraph.0","text.4"],"code":", "},{"id":"/root/children/118/children/4/children/0/children/5","type":"inlineCode","loc":{"start":22007,"end":22025,"line":{"s":640,"e":640,"code":["- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,"]},"column":{"s":52,"e":70}},"dim":["","list.118","listItem.4","paragraph.0","inlineCode.5"],"code":"`collectBodyNodes`"},{"id":"/root/children/118/children/4/children/0/children/6","type":"text","loc":{"start":22025,"end":22033,"line":{"s":640,"e":641,"code":["- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,","  and `findInHeadings` all agree on which headings are reachable."]},"column":{"s":70,"e":6}},"dim":["","list.118","listItem.4","paragraph.0","text.6"],"code":",\n  and "},{"id":"/root/children/118/children/4/children/0/children/7","type":"inlineCode","loc":{"start":22033,"end":22049,"line":{"s":641,"e":641,"code":["  and `findInHeadings` all agree on which headings are reachable."]},"column":{"s":6,"e":22}},"dim":["","list.118","listItem.4","paragraph.0","inlineCode.7"],"code":"`findInHeadings`"},{"id":"/root/children/118/children/4/children/0/children/8","type":"text","loc":{"start":22049,"end":22092,"line":{"s":641,"e":641,"code":["  and `findInHeadings` all agree on which headings are reachable."]},"column":{"s":22,"e":65}},"dim":["","list.118","listItem.4","paragraph.0","text.8"],"code":" all agree on which headings are reachable."},{"id":"/root/children/118/children/5","type":"listItem","loc":{"start":22093,"end":22251,"line":{"s":642,"e":644,"code":["- Rationale: extructions are inert markers by default; their body is","  dropped (or evaluated with `evalFn`), but document structure under","  them is preserved."]},"column":{"s":0,"e":20}},"dim":["","list.118","listItem.5"],"code":"- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved."},{"id":"/root/children/118/children/5/children/0","type":"paragraph","loc":{"start":22095,"end":22251,"line":{"s":642,"e":644,"code":["- Rationale: extructions are inert markers by default; their body is","  dropped (or evaluated with `evalFn`), but document structure under","  them is preserved."]},"column":{"s":2,"e":20}},"dim":["","list.118","listItem.5","paragraph.0"],"code":"Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved."},{"id":"/root/children/118/children/5/children/0/children/0","type":"text","loc":{"start":22095,"end":22191,"line":{"s":642,"e":643,"code":["- Rationale: extructions are inert markers by default; their body is","  dropped (or evaluated with `evalFn`), but document structure under"]},"column":{"s":2,"e":29}},"dim":["","list.118","listItem.5","paragraph.0","text.0"],"code":"Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with "},{"id":"/root/children/118/children/5/children/0/children/1","type":"inlineCode","loc":{"start":22191,"end":22199,"line":{"s":643,"e":643,"code":["  dropped (or evaluated with `evalFn`), but document structure under"]},"column":{"s":29,"e":37}},"dim":["","list.118","listItem.5","paragraph.0","inlineCode.1"],"code":"`evalFn`"},{"id":"/root/children/118/children/5/children/0/children/2","type":"text","loc":{"start":22199,"end":22251,"line":{"s":643,"e":644,"code":["  dropped (or evaluated with `evalFn`), but document structure under","  them is preserved."]},"column":{"s":37,"e":20}},"dim":["","list.118","listItem.5","paragraph.0","text.2"],"code":"), but document structure under\n  them is preserved."},{"id":"/root/children/119","type":"heading","loc":{"start":22253,"end":22271,"line":{"s":646,"e":646,"code":["## App Integration"]},"column":{"s":0,"e":18}},"dim":["","heading.119"],"code":"## App Integration","symbName":"heading","symbRange":[22273,23289],"symbRangeL":[646,665],"outerCode":"\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.","outerHtml":"\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>"},{"id":"/root/children/119/children/0","type":"text","loc":{"start":22256,"end":22271,"line":{"s":646,"e":646,"code":["## App Integration"]},"column":{"s":3,"e":18}},"dim":["","heading.119","text.0"],"code":"App Integration"},{"id":"/root/children/120","type":"paragraph","loc":{"start":22273,"end":22411,"line":{"s":648,"e":649,"code":["The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case","of the extension switch (line 876). When a `.mdt` file is opened:"]},"column":{"s":0,"e":65}},"dim":["","paragraph.120"],"code":"The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:"},{"id":"/root/children/120/children/0","type":"text","loc":{"start":22273,"end":22308,"line":{"s":648,"e":648,"code":["The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case"]},"column":{"s":0,"e":35}},"dim":["","paragraph.120","text.0"],"code":"The MDT library is integrated into "},{"id":"/root/children/120/children/1","type":"inlineCode","loc":{"start":22308,"end":22325,"line":{"s":648,"e":648,"code":["The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case"]},"column":{"s":35,"e":52}},"dim":["","paragraph.120","inlineCode.1"],"code":"`player-paper.js`"},{"id":"/root/children/120/children/2","type":"text","loc":{"start":22325,"end":22333,"line":{"s":648,"e":648,"code":["The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case"]},"column":{"s":52,"e":60}},"dim":["","paragraph.120","text.2"],"code":" at the "},{"id":"/root/children/120/children/3","type":"inlineCode","loc":{"start":22333,"end":22340,"line":{"s":648,"e":648,"code":["The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case"]},"column":{"s":60,"e":67}},"dim":["","paragraph.120","inlineCode.3"],"code":"`\"mdt\"`"},{"id":"/root/children/120/children/4","type":"text","loc":{"start":22340,"end":22389,"line":{"s":648,"e":649,"code":["The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case","of the extension switch (line 876). When a `.mdt` file is opened:"]},"column":{"s":67,"e":43}},"dim":["","paragraph.120","text.4"],"code":" case\nof the extension switch (line 876). When a "},{"id":"/root/children/120/children/5","type":"inlineCode","loc":{"start":22389,"end":22395,"line":{"s":649,"e":649,"code":["of the extension switch (line 876). When a `.mdt` file is opened:"]},"column":{"s":43,"e":49}},"dim":["","paragraph.120","inlineCode.5"],"code":"`.mdt`"},{"id":"/root/children/120/children/6","type":"text","loc":{"start":22395,"end":22411,"line":{"s":649,"e":649,"code":["of the extension switch (line 876). When a `.mdt` file is opened:"]},"column":{"s":49,"e":65}},"dim":["","paragraph.120","text.6"],"code":" file is opened:"},{"id":"/root/children/121","type":"list","loc":{"start":22413,"end":23078,"line":{"s":651,"e":660,"code":["1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN","   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`","2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching","3. **Compile**: `compile(data, { remark })` → `Runner`","4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)","5. **Rebuild clean markdown**: fragments recursively collected via","   `collectFragments()` async generator, each fragment's `toString()`","   produces heading + body with extructions already filtered","6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`","7. **Post-process**: heading tabindex, relative image URL resolution"]},"column":{"s":0,"e":68}},"dim":["","list.121"],"code":"1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution","symbName":"list","symbRange":[23080,24334],"symbRangeL":[651,707],"outerCode":"   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):","outerHtml":"<p>   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</p><ol><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>"},{"id":"/root/children/121/children/0","type":"listItem","loc":{"start":22413,"end":22542,"line":{"s":651,"e":652,"code":["1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN","   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`"]},"column":{"s":0,"e":63}},"dim":["","list.121","listItem.0"],"code":"1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`"},{"id":"/root/children/121/children/0/children/0","type":"paragraph","loc":{"start":22416,"end":22542,"line":{"s":651,"e":652,"code":["1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN","   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`"]},"column":{"s":3,"e":63}},"dim":["","list.121","listItem.0","paragraph.0"],"code":"**Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`"},{"id":"/root/children/121/children/0/children/0/children/0","type":"strong","loc":{"start":22416,"end":22435,"line":{"s":651,"e":651,"code":["1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN"]},"column":{"s":3,"e":22}},"dim":["","list.121","listItem.0","paragraph.0","strong.0"],"code":"**Dynamic imports**"},{"id":"/root/children/121/children/0/children/0/children/0/children/0","type":"text","loc":{"start":22418,"end":22433,"line":{"s":651,"e":651,"code":["1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN"]},"column":{"s":5,"e":20}},"dim":["","list.121","listItem.0","paragraph.0","strong.0","text.0"],"code":"Dynamic imports"},{"id":"/root/children/121/children/0/children/0/children/1","type":"text","loc":{"start":22435,"end":22437,"line":{"s":651,"e":651,"code":["1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN"]},"column":{"s":22,"e":24}},"dim":["","list.121","listItem.0","paragraph.0","text.1"],"code":": "},{"id":"/root/children/121/children/0/children/0/children/2","type":"inlineCode","loc":{"start":22437,"end":22445,"line":{"s":651,"e":651,"code":["1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN"]},"column":{"s":24,"e":32}},"dim":["","list.121","listItem.0","paragraph.0","inlineCode.2"],"code":"`remark`"},{"id":"/root/children/121/children/0/children/0/children/3","type":"text","loc":{"start":22445,"end":22448,"line":{"s":651,"e":651,"code":["1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN"]},"column":{"s":32,"e":35}},"dim":["","list.121","listItem.0","paragraph.0","text.3"],"code":" + "},{"id":"/root/children/121/children/0/children/0/children/4","type":"inlineCode","loc":{"start":22448,"end":22462,"line":{"s":651,"e":651,"code":["1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN"]},"column":{"s":35,"e":49}},"dim":["","list.121","listItem.0","paragraph.0","inlineCode.4"],"code":"`remark-parse`"},{"id":"/root/children/121/children/0/children/0/children/5","type":"text","loc":{"start":22462,"end":22483,"line":{"s":651,"e":652,"code":["1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN","   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`"]},"column":{"s":49,"e":4}},"dim":["","list.121","listItem.0","paragraph.0","text.5"],"code":" loaded from CDN\n   ("},{"id":"/root/children/121/children/0/children/0/children/6","type":"inlineCode","loc":{"start":22483,"end":22501,"line":{"s":652,"e":652,"code":["   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`"]},"column":{"s":4,"e":22}},"dim":["","list.121","listItem.0","paragraph.0","inlineCode.6"],"code":"`cdn.jsdelivr.net`"},{"id":"/root/children/121/children/0/children/0/children/7","type":"text","loc":{"start":22501,"end":22504,"line":{"s":652,"e":652,"code":["   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`"]},"column":{"s":22,"e":25}},"dim":["","list.121","listItem.0","paragraph.0","text.7"],"code":"); "},{"id":"/root/children/121/children/0/children/0/children/8","type":"inlineCode","loc":{"start":22504,"end":22513,"line":{"s":652,"e":652,"code":["   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`"]},"column":{"s":25,"e":34}},"dim":["","list.121","listItem.0","paragraph.0","inlineCode.8"],"code":"`compile`"},{"id":"/root/children/121/children/0/children/0/children/9","type":"text","loc":{"start":22513,"end":22528,"line":{"s":652,"e":652,"code":["   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`"]},"column":{"s":34,"e":49}},"dim":["","list.121","listItem.0","paragraph.0","text.9"],"code":" imported from "},{"id":"/root/children/121/children/0/children/0/children/10","type":"inlineCode","loc":{"start":22528,"end":22542,"line":{"s":652,"e":652,"code":["   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`"]},"column":{"s":49,"e":63}},"dim":["","list.121","listItem.0","paragraph.0","inlineCode.10"],"code":"`./mdt/mdt.js`"},{"id":"/root/children/121/children/1","type":"listItem","loc":{"start":22543,"end":22622,"line":{"s":653,"e":653,"code":["2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching"]},"column":{"s":0,"e":79}},"dim":["","list.121","listItem.1"],"code":"2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching"},{"id":"/root/children/121/children/1/children/0","type":"paragraph","loc":{"start":22546,"end":22622,"line":{"s":653,"e":653,"code":["2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching"]},"column":{"s":3,"e":79}},"dim":["","list.121","listItem.1","paragraph.0"],"code":"**Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching"},{"id":"/root/children/121/children/1/children/0/children/0","type":"strong","loc":{"start":22546,"end":22555,"line":{"s":653,"e":653,"code":["2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching"]},"column":{"s":3,"e":12}},"dim":["","list.121","listItem.1","paragraph.0","strong.0"],"code":"**Fetch**"},{"id":"/root/children/121/children/1/children/0/children/0/children/0","type":"text","loc":{"start":22548,"end":22553,"line":{"s":653,"e":653,"code":["2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching"]},"column":{"s":5,"e":10}},"dim":["","list.121","listItem.1","paragraph.0","strong.0","text.0"],"code":"Fetch"},{"id":"/root/children/121/children/1/children/0/children/1","type":"text","loc":{"start":22555,"end":22582,"line":{"s":653,"e":653,"code":["2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching"]},"column":{"s":12,"e":39}},"dim":["","list.121","listItem.1","paragraph.0","text.1"],"code":": file content fetched via "},{"id":"/root/children/121/children/1/children/0/children/2","type":"inlineCode","loc":{"start":22582,"end":22604,"line":{"s":653,"e":653,"code":["2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching"]},"column":{"s":39,"e":61}},"dim":["","list.121","listItem.1","paragraph.0","inlineCode.2"],"code":"`ssss.fetchWithETag()`"},{"id":"/root/children/121/children/1/children/0/children/3","type":"text","loc":{"start":22604,"end":22622,"line":{"s":653,"e":653,"code":["2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching"]},"column":{"s":61,"e":79}},"dim":["","list.121","listItem.1","paragraph.0","text.3"],"code":" with ETag caching"},{"id":"/root/children/121/children/2","type":"listItem","loc":{"start":22623,"end":22677,"line":{"s":654,"e":654,"code":["3. **Compile**: `compile(data, { remark })` → `Runner`"]},"column":{"s":0,"e":54}},"dim":["","list.121","listItem.2"],"code":"3. **Compile**: `compile(data, { remark })` → `Runner`"},{"id":"/root/children/121/children/2/children/0","type":"paragraph","loc":{"start":22626,"end":22677,"line":{"s":654,"e":654,"code":["3. **Compile**: `compile(data, { remark })` → `Runner`"]},"column":{"s":3,"e":54}},"dim":["","list.121","listItem.2","paragraph.0"],"code":"**Compile**: `compile(data, { remark })` → `Runner`"},{"id":"/root/children/121/children/2/children/0/children/0","type":"strong","loc":{"start":22626,"end":22637,"line":{"s":654,"e":654,"code":["3. **Compile**: `compile(data, { remark })` → `Runner`"]},"column":{"s":3,"e":14}},"dim":["","list.121","listItem.2","paragraph.0","strong.0"],"code":"**Compile**"},{"id":"/root/children/121/children/2/children/0/children/0/children/0","type":"text","loc":{"start":22628,"end":22635,"line":{"s":654,"e":654,"code":["3. **Compile**: `compile(data, { remark })` → `Runner`"]},"column":{"s":5,"e":12}},"dim":["","list.121","listItem.2","paragraph.0","strong.0","text.0"],"code":"Compile"},{"id":"/root/children/121/children/2/children/0/children/1","type":"text","loc":{"start":22637,"end":22639,"line":{"s":654,"e":654,"code":["3. **Compile**: `compile(data, { remark })` → `Runner`"]},"column":{"s":14,"e":16}},"dim":["","list.121","listItem.2","paragraph.0","text.1"],"code":": "},{"id":"/root/children/121/children/2/children/0/children/2","type":"inlineCode","loc":{"start":22639,"end":22666,"line":{"s":654,"e":654,"code":["3. **Compile**: `compile(data, { remark })` → `Runner`"]},"column":{"s":16,"e":43}},"dim":["","list.121","listItem.2","paragraph.0","inlineCode.2"],"code":"`compile(data, { remark })`"},{"id":"/root/children/121/children/2/children/0/children/3","type":"text","loc":{"start":22666,"end":22669,"line":{"s":654,"e":654,"code":["3. **Compile**: `compile(data, { remark })` → `Runner`"]},"column":{"s":43,"e":46}},"dim":["","list.121","listItem.2","paragraph.0","text.3"],"code":" → "},{"id":"/root/children/121/children/2/children/0/children/4","type":"inlineCode","loc":{"start":22669,"end":22677,"line":{"s":654,"e":654,"code":["3. **Compile**: `compile(data, { remark })` → `Runner`"]},"column":{"s":46,"e":54}},"dim":["","list.121","listItem.2","paragraph.0","inlineCode.4"],"code":"`Runner`"},{"id":"/root/children/121/children/3","type":"listItem","loc":{"start":22678,"end":22744,"line":{"s":655,"e":655,"code":["4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)"]},"column":{"s":0,"e":66}},"dim":["","list.121","listItem.3"],"code":"4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)"},{"id":"/root/children/121/children/3/children/0","type":"paragraph","loc":{"start":22681,"end":22744,"line":{"s":655,"e":655,"code":["4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)"]},"column":{"s":3,"e":66}},"dim":["","list.121","listItem.3","paragraph.0"],"code":"**Run**: `runner(STATE)` → `Document` (STATE serves as context)"},{"id":"/root/children/121/children/3/children/0/children/0","type":"strong","loc":{"start":22681,"end":22688,"line":{"s":655,"e":655,"code":["4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)"]},"column":{"s":3,"e":10}},"dim":["","list.121","listItem.3","paragraph.0","strong.0"],"code":"**Run**"},{"id":"/root/children/121/children/3/children/0/children/0/children/0","type":"text","loc":{"start":22683,"end":22686,"line":{"s":655,"e":655,"code":["4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)"]},"column":{"s":5,"e":8}},"dim":["","list.121","listItem.3","paragraph.0","strong.0","text.0"],"code":"Run"},{"id":"/root/children/121/children/3/children/0/children/1","type":"text","loc":{"start":22688,"end":22690,"line":{"s":655,"e":655,"code":["4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)"]},"column":{"s":10,"e":12}},"dim":["","list.121","listItem.3","paragraph.0","text.1"],"code":": "},{"id":"/root/children/121/children/3/children/0/children/2","type":"inlineCode","loc":{"start":22690,"end":22705,"line":{"s":655,"e":655,"code":["4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)"]},"column":{"s":12,"e":27}},"dim":["","list.121","listItem.3","paragraph.0","inlineCode.2"],"code":"`runner(STATE)`"},{"id":"/root/children/121/children/3/children/0/children/3","type":"text","loc":{"start":22705,"end":22708,"line":{"s":655,"e":655,"code":["4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)"]},"column":{"s":27,"e":30}},"dim":["","list.121","listItem.3","paragraph.0","text.3"],"code":" → "},{"id":"/root/children/121/children/3/children/0/children/4","type":"inlineCode","loc":{"start":22708,"end":22718,"line":{"s":655,"e":655,"code":["4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)"]},"column":{"s":30,"e":40}},"dim":["","list.121","listItem.3","paragraph.0","inlineCode.4"],"code":"`Document`"},{"id":"/root/children/121/children/3/children/0/children/5","type":"text","loc":{"start":22718,"end":22744,"line":{"s":655,"e":655,"code":["4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)"]},"column":{"s":40,"e":66}},"dim":["","list.121","listItem.3","paragraph.0","text.5"],"code":" (STATE serves as context)"},{"id":"/root/children/121/children/4","type":"listItem","loc":{"start":22745,"end":22942,"line":{"s":656,"e":658,"code":["5. **Rebuild clean markdown**: fragments recursively collected via","   `collectFragments()` async generator, each fragment's `toString()`","   produces heading + body with extructions already filtered"]},"column":{"s":0,"e":60}},"dim":["","list.121","listItem.4"],"code":"5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered"},{"id":"/root/children/121/children/4/children/0","type":"paragraph","loc":{"start":22748,"end":22942,"line":{"s":656,"e":658,"code":["5. **Rebuild clean markdown**: fragments recursively collected via","   `collectFragments()` async generator, each fragment's `toString()`","   produces heading + body with extructions already filtered"]},"column":{"s":3,"e":60}},"dim":["","list.121","listItem.4","paragraph.0"],"code":"**Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered"},{"id":"/root/children/121/children/4/children/0/children/0","type":"strong","loc":{"start":22748,"end":22774,"line":{"s":656,"e":656,"code":["5. **Rebuild clean markdown**: fragments recursively collected via"]},"column":{"s":3,"e":29}},"dim":["","list.121","listItem.4","paragraph.0","strong.0"],"code":"**Rebuild clean markdown**"},{"id":"/root/children/121/children/4/children/0/children/0/children/0","type":"text","loc":{"start":22750,"end":22772,"line":{"s":656,"e":656,"code":["5. **Rebuild clean markdown**: fragments recursively collected via"]},"column":{"s":5,"e":27}},"dim":["","list.121","listItem.4","paragraph.0","strong.0","text.0"],"code":"Rebuild clean markdown"},{"id":"/root/children/121/children/4/children/0/children/1","type":"text","loc":{"start":22774,"end":22812,"line":{"s":656,"e":657,"code":["5. **Rebuild clean markdown**: fragments recursively collected via","   `collectFragments()` async generator, each fragment's `toString()`"]},"column":{"s":29,"e":0}},"dim":["","list.121","listItem.4","paragraph.0","text.1"],"code":": fragments recursively collected via\n"},{"id":"/root/children/121/children/4/children/0/children/2","type":"inlineCode","loc":{"start":22815,"end":22835,"line":{"s":657,"e":657,"code":["   `collectFragments()` async generator, each fragment's `toString()`"]},"column":{"s":3,"e":23}},"dim":["","list.121","listItem.4","paragraph.0","inlineCode.2"],"code":"`collectFragments()`"},{"id":"/root/children/121/children/4/children/0/children/3","type":"text","loc":{"start":22835,"end":22869,"line":{"s":657,"e":657,"code":["   `collectFragments()` async generator, each fragment's `toString()`"]},"column":{"s":23,"e":57}},"dim":["","list.121","listItem.4","paragraph.0","text.3"],"code":" async generator, each fragment's "},{"id":"/root/children/121/children/4/children/0/children/4","type":"inlineCode","loc":{"start":22869,"end":22881,"line":{"s":657,"e":657,"code":["   `collectFragments()` async generator, each fragment's `toString()`"]},"column":{"s":57,"e":69}},"dim":["","list.121","listItem.4","paragraph.0","inlineCode.4"],"code":"`toString()`"},{"id":"/root/children/121/children/4/children/0/children/5","type":"text","loc":{"start":22881,"end":22942,"line":{"s":657,"e":658,"code":["   `collectFragments()` async generator, each fragment's `toString()`","   produces heading + body with extructions already filtered"]},"column":{"s":69,"e":60}},"dim":["","list.121","listItem.4","paragraph.0","text.5"],"code":"\n   produces heading + body with extructions already filtered"},{"id":"/root/children/121/children/5","type":"listItem","loc":{"start":22943,"end":23009,"line":{"s":659,"e":659,"code":["6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`"]},"column":{"s":0,"e":66}},"dim":["","list.121","listItem.5"],"code":"6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`"},{"id":"/root/children/121/children/5/children/0","type":"paragraph","loc":{"start":22946,"end":23009,"line":{"s":659,"e":659,"code":["6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`"]},"column":{"s":3,"e":66}},"dim":["","list.121","listItem.5","paragraph.0"],"code":"**Render**: clean markdown rendered via `ssss.renderMarkdown()`"},{"id":"/root/children/121/children/5/children/0/children/0","type":"strong","loc":{"start":22946,"end":22956,"line":{"s":659,"e":659,"code":["6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`"]},"column":{"s":3,"e":13}},"dim":["","list.121","listItem.5","paragraph.0","strong.0"],"code":"**Render**"},{"id":"/root/children/121/children/5/children/0/children/0/children/0","type":"text","loc":{"start":22948,"end":22954,"line":{"s":659,"e":659,"code":["6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`"]},"column":{"s":5,"e":11}},"dim":["","list.121","listItem.5","paragraph.0","strong.0","text.0"],"code":"Render"},{"id":"/root/children/121/children/5/children/0/children/1","type":"text","loc":{"start":22956,"end":22986,"line":{"s":659,"e":659,"code":["6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`"]},"column":{"s":13,"e":43}},"dim":["","list.121","listItem.5","paragraph.0","text.1"],"code":": clean markdown rendered via "},{"id":"/root/children/121/children/5/children/0/children/2","type":"inlineCode","loc":{"start":22986,"end":23009,"line":{"s":659,"e":659,"code":["6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`"]},"column":{"s":43,"e":66}},"dim":["","list.121","listItem.5","paragraph.0","inlineCode.2"],"code":"`ssss.renderMarkdown()`"},{"id":"/root/children/121/children/6","type":"listItem","loc":{"start":23010,"end":23078,"line":{"s":660,"e":660,"code":["7. **Post-process**: heading tabindex, relative image URL resolution"]},"column":{"s":0,"e":68}},"dim":["","list.121","listItem.6"],"code":"7. **Post-process**: heading tabindex, relative image URL resolution"},{"id":"/root/children/121/children/6/children/0","type":"paragraph","loc":{"start":23013,"end":23078,"line":{"s":660,"e":660,"code":["7. **Post-process**: heading tabindex, relative image URL resolution"]},"column":{"s":3,"e":68}},"dim":["","list.121","listItem.6","paragraph.0"],"code":"**Post-process**: heading tabindex, relative image URL resolution"},{"id":"/root/children/121/children/6/children/0/children/0","type":"strong","loc":{"start":23013,"end":23029,"line":{"s":660,"e":660,"code":["7. **Post-process**: heading tabindex, relative image URL resolution"]},"column":{"s":3,"e":19}},"dim":["","list.121","listItem.6","paragraph.0","strong.0"],"code":"**Post-process**"},{"id":"/root/children/121/children/6/children/0/children/0/children/0","type":"text","loc":{"start":23015,"end":23027,"line":{"s":660,"e":660,"code":["7. **Post-process**: heading tabindex, relative image URL resolution"]},"column":{"s":5,"e":17}},"dim":["","list.121","listItem.6","paragraph.0","strong.0","text.0"],"code":"Post-process"},{"id":"/root/children/121/children/6/children/0/children/1","type":"text","loc":{"start":23029,"end":23078,"line":{"s":660,"e":660,"code":["7. **Post-process**: heading tabindex, relative image URL resolution"]},"column":{"s":19,"e":68}},"dim":["","list.121","listItem.6","paragraph.0","text.1"],"code":": heading tabindex, relative image URL resolution"},{"id":"/root/children/122","type":"paragraph","loc":{"start":23080,"end":23289,"line":{"s":662,"e":664,"code":["The current integration uses the browser's dynamic `import()` for remark","(same CDN source as `mdd.mjs`). The `context` parameter passes the app's","STATE object, with adapters mixed in for extruction evaluation."]},"column":{"s":0,"e":63}},"dim":["","paragraph.122"],"code":"The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation."},{"id":"/root/children/122/children/0","type":"text","loc":{"start":23080,"end":23131,"line":{"s":662,"e":662,"code":["The current integration uses the browser's dynamic `import()` for remark"]},"column":{"s":0,"e":51}},"dim":["","paragraph.122","text.0"],"code":"The current integration uses the browser's dynamic "},{"id":"/root/children/122/children/1","type":"inlineCode","loc":{"start":23131,"end":23141,"line":{"s":662,"e":662,"code":["The current integration uses the browser's dynamic `import()` for remark"]},"column":{"s":51,"e":61}},"dim":["","paragraph.122","inlineCode.1"],"code":"`import()`"},{"id":"/root/children/122/children/2","type":"text","loc":{"start":23141,"end":23173,"line":{"s":662,"e":663,"code":["The current integration uses the browser's dynamic `import()` for remark","(same CDN source as `mdd.mjs`). The `context` parameter passes the app's"]},"column":{"s":61,"e":20}},"dim":["","paragraph.122","text.2"],"code":" for remark\n(same CDN source as "},{"id":"/root/children/122/children/3","type":"inlineCode","loc":{"start":23173,"end":23182,"line":{"s":663,"e":663,"code":["(same CDN source as `mdd.mjs`). The `context` parameter passes the app's"]},"column":{"s":20,"e":29}},"dim":["","paragraph.122","inlineCode.3"],"code":"`mdd.mjs`"},{"id":"/root/children/122/children/4","type":"text","loc":{"start":23182,"end":23189,"line":{"s":663,"e":663,"code":["(same CDN source as `mdd.mjs`). The `context` parameter passes the app's"]},"column":{"s":29,"e":36}},"dim":["","paragraph.122","text.4"],"code":"). The "},{"id":"/root/children/122/children/5","type":"inlineCode","loc":{"start":23189,"end":23198,"line":{"s":663,"e":663,"code":["(same CDN source as `mdd.mjs`). The `context` parameter passes the app's"]},"column":{"s":36,"e":45}},"dim":["","paragraph.122","inlineCode.5"],"code":"`context`"},{"id":"/root/children/122/children/6","type":"text","loc":{"start":23198,"end":23289,"line":{"s":663,"e":664,"code":["(same CDN source as `mdd.mjs`). The `context` parameter passes the app's","STATE object, with adapters mixed in for extruction evaluation."]},"column":{"s":45,"e":63}},"dim":["","paragraph.122","text.6"],"code":" parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation."},{"id":"/root/children/123","type":"heading","loc":{"start":23291,"end":23315,"line":{"s":666,"e":666,"code":["## Extruction Evaluation"]},"column":{"s":0,"e":24}},"dim":["","heading.123"],"code":"## Extruction Evaluation","symbName":"heading","symbRange":[23317,23487],"symbRangeL":[666,671],"outerCode":"\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.","outerHtml":"\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>"},{"id":"/root/children/123/children/0","type":"text","loc":{"start":23294,"end":23315,"line":{"s":666,"e":666,"code":["## Extruction Evaluation"]},"column":{"s":3,"e":24}},"dim":["","heading.123","text.0"],"code":"Extruction Evaluation"},{"id":"/root/children/124","type":"paragraph","loc":{"start":23317,"end":23487,"line":{"s":668,"e":670,"code":["Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`","option is passed to the runner. This enables `# ${...}` headings to produce","dynamic content."]},"column":{"s":0,"e":16}},"dim":["","paragraph.124"],"code":"Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content."},{"id":"/root/children/124/children/0","type":"text","loc":{"start":23317,"end":23386,"line":{"s":668,"e":668,"code":["Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`"]},"column":{"s":0,"e":69}},"dim":["","paragraph.124","text.0"],"code":"Extruction bodies can be evaluated as JavaScript at runtime when the "},{"id":"/root/children/124/children/1","type":"inlineCode","loc":{"start":23386,"end":23394,"line":{"s":668,"e":668,"code":["Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`"]},"column":{"s":69,"e":77}},"dim":["","paragraph.124","inlineCode.1"],"code":"`evalFn`"},{"id":"/root/children/124/children/2","type":"text","loc":{"start":23394,"end":23440,"line":{"s":668,"e":669,"code":["Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`","option is passed to the runner. This enables `# ${...}` headings to produce"]},"column":{"s":77,"e":45}},"dim":["","paragraph.124","text.2"],"code":"\noption is passed to the runner. This enables "},{"id":"/root/children/124/children/3","type":"inlineCode","loc":{"start":23440,"end":23450,"line":{"s":669,"e":669,"code":["option is passed to the runner. This enables `# ${...}` headings to produce"]},"column":{"s":45,"e":55}},"dim":["","paragraph.124","inlineCode.3"],"code":"`# ${...}`"},{"id":"/root/children/124/children/4","type":"text","loc":{"start":23450,"end":23487,"line":{"s":669,"e":670,"code":["option is passed to the runner. This enables `# ${...}` headings to produce","dynamic content."]},"column":{"s":55,"e":16}},"dim":["","paragraph.124","text.4"],"code":" headings to produce\ndynamic content."},{"id":"/root/children/125","type":"heading","loc":{"start":23489,"end":23501,"line":{"s":672,"e":672,"code":["### evalBody"]},"column":{"s":0,"e":12}},"dim":["","heading.125"],"code":"### evalBody","symbName":"heading","symbRange":[23503,24132],"symbRangeL":[672,702],"outerCode":"\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```","outerHtml":"\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>"},{"id":"/root/children/125/children/0","type":"text","loc":{"start":23493,"end":23501,"line":{"s":672,"e":672,"code":["### evalBody"]},"column":{"s":4,"e":12}},"dim":["","heading.125","text.0"],"code":"evalBody"},{"id":"/root/children/126","type":"paragraph","loc":{"start":23503,"end":23562,"line":{"s":674,"e":674,"code":["`mdt/eval-body.js` exports the default evaluation function:"]},"column":{"s":0,"e":59}},"dim":["","paragraph.126"],"code":"`mdt/eval-body.js` exports the default evaluation function:"},{"id":"/root/children/126/children/0","type":"inlineCode","loc":{"start":23503,"end":23521,"line":{"s":674,"e":674,"code":["`mdt/eval-body.js` exports the default evaluation function:"]},"column":{"s":0,"e":18}},"dim":["","paragraph.126","inlineCode.0"],"code":"`mdt/eval-body.js`"},{"id":"/root/children/126/children/1","type":"text","loc":{"start":23521,"end":23562,"line":{"s":674,"e":674,"code":["`mdt/eval-body.js` exports the default evaluation function:"]},"column":{"s":18,"e":59}},"dim":["","paragraph.126","text.1"],"code":" exports the default evaluation function:"},{"id":"/root/children/127","type":"code","loc":{"start":23565,"end":23615,"line":{"s":677,"e":679,"code":["```","evalBody(bodyText, context) → Promise<any>","```"]},"column":{"s":0,"e":3}},"dim":["","code.127"],"code":"```\nevalBody(bodyText, context) → Promise<any>\n```","symbName":"code","symbRange":[23617,23810],"symbRangeL":[null,684],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>"},{"id":"/root/children/128","type":"paragraph","loc":{"start":23617,"end":23810,"line":{"s":681,"e":683,"code":["It uses the `AsyncFunction` constructor (same pattern as","`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as","JS code with the context keys available as named parameters."]},"column":{"s":0,"e":60}},"dim":["","paragraph.128"],"code":"It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters."},{"id":"/root/children/128/children/0","type":"text","loc":{"start":23617,"end":23629,"line":{"s":681,"e":681,"code":["It uses the `AsyncFunction` constructor (same pattern as"]},"column":{"s":0,"e":12}},"dim":["","paragraph.128","text.0"],"code":"It uses the "},{"id":"/root/children/128/children/1","type":"inlineCode","loc":{"start":23629,"end":23644,"line":{"s":681,"e":681,"code":["It uses the `AsyncFunction` constructor (same pattern as"]},"column":{"s":12,"e":27}},"dim":["","paragraph.128","inlineCode.1"],"code":"`AsyncFunction`"},{"id":"/root/children/128/children/2","type":"text","loc":{"start":23644,"end":23674,"line":{"s":681,"e":682,"code":["It uses the `AsyncFunction` constructor (same pattern as","`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as"]},"column":{"s":27,"e":0}},"dim":["","paragraph.128","text.2"],"code":" constructor (same pattern as\n"},{"id":"/root/children/128/children/3","type":"inlineCode","loc":{"start":23674,"end":23699,"line":{"s":682,"e":682,"code":["`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as"]},"column":{"s":0,"e":25}},"dim":["","paragraph.128","inlineCode.3"],"code":"`evalJsFilterWithContext`"},{"id":"/root/children/128/children/4","type":"text","loc":{"start":23699,"end":23703,"line":{"s":682,"e":682,"code":["`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as"]},"column":{"s":25,"e":29}},"dim":["","paragraph.128","text.4"],"code":" in "},{"id":"/root/children/128/children/5","type":"inlineCode","loc":{"start":23703,"end":23719,"line":{"s":682,"e":682,"code":["`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as"]},"column":{"s":29,"e":45}},"dim":["","paragraph.128","inlineCode.5"],"code":"`filter-base.js`"},{"id":"/root/children/128/children/6","type":"text","loc":{"start":23719,"end":23810,"line":{"s":682,"e":683,"code":["`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as","JS code with the context keys available as named parameters."]},"column":{"s":45,"e":60}},"dim":["","paragraph.128","text.6"],"code":") to evaluate the body text as\nJS code with the context keys available as named parameters."},{"id":"/root/children/129","type":"code","loc":{"start":23812,"end":23930,"line":{"s":685,"e":689,"code":["```js","import { evalBody } from \"./mdt/eval-body.js\";","","const doc = runner({ search, STATE }, { evalFn: evalBody });","```"]},"column":{"s":0,"e":3}},"dim":["","code.129"],"code":"```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```","symbName":"code","symbRange":[23932,24007],"symbRangeL":[null,693],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n"},{"id":"/root/children/130","type":"paragraph","loc":{"start":23932,"end":24007,"line":{"s":691,"e":691,"code":["Inside an extruction body, any key from the context is directly accessible:"]},"column":{"s":0,"e":75}},"dim":["","paragraph.130"],"code":"Inside an extruction body, any key from the context is directly accessible:"},{"id":"/root/children/130/children/0","type":"text","loc":{"start":23932,"end":24007,"line":{"s":691,"e":691,"code":["Inside an extruction body, any key from the context is directly accessible:"]},"column":{"s":0,"e":75}},"dim":["","paragraph.130","text.0"],"code":"Inside an extruction body, any key from the context is directly accessible:"},{"id":"/root/children/131","type":"code","loc":{"start":24010,"end":24132,"line":{"s":694,"e":701,"code":["```","## ${the list}","","\\`\\`\\`javascript","const x = await search(\"mdd\")","return insert( x.map(i => i.uri).join(\"\\n\"))","\\`\\`\\`","```"]},"column":{"s":0,"e":3}},"dim":["","code.131"],"code":"```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```","symbName":"code","symbRange":[24134,24807],"symbRangeL":[null,717],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n"},{"id":"/root/children/132","type":"heading","loc":{"start":24134,"end":24197,"line":{"s":703,"e":703,"code":["### Extruction return value — `insert()` / `inject()` built-ins"]},"column":{"s":0,"e":63}},"dim":["","heading.132"],"code":"### Extruction return value — `insert()` / `inject()` built-ins","symbName":"heading","symbRange":[24199,24632],"symbRangeL":[703,711],"outerCode":"\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)","outerHtml":"\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>"},{"id":"/root/children/132/children/0","type":"text","loc":{"start":24138,"end":24164,"line":{"s":703,"e":703,"code":["### Extruction return value — `insert()` / `inject()` built-ins"]},"column":{"s":4,"e":30}},"dim":["","heading.132","text.0"],"code":"Extruction return value — "},{"id":"/root/children/132/children/1","type":"inlineCode","loc":{"start":24164,"end":24174,"line":{"s":703,"e":703,"code":["### Extruction return value — `insert()` / `inject()` built-ins"]},"column":{"s":30,"e":40}},"dim":["","heading.132","inlineCode.1"],"code":"`insert()`"},{"id":"/root/children/132/children/2","type":"text","loc":{"start":24174,"end":24177,"line":{"s":703,"e":703,"code":["### Extruction return value — `insert()` / `inject()` built-ins"]},"column":{"s":40,"e":43}},"dim":["","heading.132","text.2"],"code":" / "},{"id":"/root/children/132/children/3","type":"inlineCode","loc":{"start":24177,"end":24187,"line":{"s":703,"e":703,"code":["### Extruction return value — `insert()` / `inject()` built-ins"]},"column":{"s":43,"e":53}},"dim":["","heading.132","inlineCode.3"],"code":"`inject()`"},{"id":"/root/children/132/children/4","type":"text","loc":{"start":24187,"end":24197,"line":{"s":703,"e":703,"code":["### Extruction return value — `insert()` / `inject()` built-ins"]},"column":{"s":53,"e":63}},"dim":["","heading.132","text.4"],"code":" built-ins"},{"id":"/root/children/133","type":"paragraph","loc":{"start":24199,"end":24334,"line":{"s":705,"e":706,"code":["When `evalFn` is provided, the extruction body has access to auto-injected","helpers and data (like `_mdt_label`, `mdtState`, and `log`):"]},"column":{"s":0,"e":60}},"dim":["","paragraph.133"],"code":"When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):"},{"id":"/root/children/133/children/0","type":"text","loc":{"start":24199,"end":24204,"line":{"s":705,"e":705,"code":["When `evalFn` is provided, the extruction body has access to auto-injected"]},"column":{"s":0,"e":5}},"dim":["","paragraph.133","text.0"],"code":"When "},{"id":"/root/children/133/children/1","type":"inlineCode","loc":{"start":24204,"end":24212,"line":{"s":705,"e":705,"code":["When `evalFn` is provided, the extruction body has access to auto-injected"]},"column":{"s":5,"e":13}},"dim":["","paragraph.133","inlineCode.1"],"code":"`evalFn`"},{"id":"/root/children/133/children/2","type":"text","loc":{"start":24212,"end":24297,"line":{"s":705,"e":706,"code":["When `evalFn` is provided, the extruction body has access to auto-injected","helpers and data (like `_mdt_label`, `mdtState`, and `log`):"]},"column":{"s":13,"e":23}},"dim":["","paragraph.133","text.2"],"code":" is provided, the extruction body has access to auto-injected\nhelpers and data (like "},{"id":"/root/children/133/children/3","type":"inlineCode","loc":{"start":24297,"end":24309,"line":{"s":706,"e":706,"code":["helpers and data (like `_mdt_label`, `mdtState`, and `log`):"]},"column":{"s":23,"e":35}},"dim":["","paragraph.133","inlineCode.3"],"code":"`_mdt_label`"},{"id":"/root/children/133/children/4","type":"text","loc":{"start":24309,"end":24311,"line":{"s":706,"e":706,"code":["helpers and data (like `_mdt_label`, `mdtState`, and `log`):"]},"column":{"s":35,"e":37}},"dim":["","paragraph.133","text.4"],"code":", "},{"id":"/root/children/133/children/5","type":"inlineCode","loc":{"start":24311,"end":24321,"line":{"s":706,"e":706,"code":["helpers and data (like `_mdt_label`, `mdtState`, and `log`):"]},"column":{"s":37,"e":47}},"dim":["","paragraph.133","inlineCode.5"],"code":"`mdtState`"},{"id":"/root/children/133/children/6","type":"text","loc":{"start":24321,"end":24327,"line":{"s":706,"e":706,"code":["helpers and data (like `_mdt_label`, `mdtState`, and `log`):"]},"column":{"s":47,"e":53}},"dim":["","paragraph.133","text.6"],"code":", and "},{"id":"/root/children/133/children/7","type":"inlineCode","loc":{"start":24327,"end":24332,"line":{"s":706,"e":706,"code":["helpers and data (like `_mdt_label`, `mdtState`, and `log`):"]},"column":{"s":53,"e":58}},"dim":["","paragraph.133","inlineCode.7"],"code":"`log`"},{"id":"/root/children/133/children/8","type":"text","loc":{"start":24332,"end":24334,"line":{"s":706,"e":706,"code":["helpers and data (like `_mdt_label`, `mdtState`, and `log`):"]},"column":{"s":58,"e":60}},"dim":["","paragraph.133","text.8"],"code":"):"},{"id":"/root/children/134","type":"list","loc":{"start":24336,"end":24632,"line":{"s":708,"e":710,"code":["- **`insert(children)`** — pipe Fragment-like objects directly into the output","- **`inject(text)`** — produce a single raw-body Fragment with no heading","- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)"]},"column":{"s":0,"e":143}},"dim":["","list.134"],"code":"- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)","symbName":"list","symbRange":[24634,27675],"symbRangeL":[708,792],"outerCode":"- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:","outerHtml":"<ul><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>"},{"id":"/root/children/134/children/0","type":"listItem","loc":{"start":24336,"end":24414,"line":{"s":708,"e":708,"code":["- **`insert(children)`** — pipe Fragment-like objects directly into the output"]},"column":{"s":0,"e":78}},"dim":["","list.134","listItem.0"],"code":"- **`insert(children)`** — pipe Fragment-like objects directly into the output"},{"id":"/root/children/134/children/0/children/0","type":"paragraph","loc":{"start":24338,"end":24414,"line":{"s":708,"e":708,"code":["- **`insert(children)`** — pipe Fragment-like objects directly into the output"]},"column":{"s":2,"e":78}},"dim":["","list.134","listItem.0","paragraph.0"],"code":"**`insert(children)`** — pipe Fragment-like objects directly into the output"},{"id":"/root/children/134/children/0/children/0/children/0","type":"strong","loc":{"start":24338,"end":24360,"line":{"s":708,"e":708,"code":["- **`insert(children)`** — pipe Fragment-like objects directly into the output"]},"column":{"s":2,"e":24}},"dim":["","list.134","listItem.0","paragraph.0","strong.0"],"code":"**`insert(children)`**"},{"id":"/root/children/134/children/0/children/0/children/0/children/0","type":"inlineCode","loc":{"start":24340,"end":24358,"line":{"s":708,"e":708,"code":["- **`insert(children)`** — pipe Fragment-like objects directly into the output"]},"column":{"s":4,"e":22}},"dim":["","list.134","listItem.0","paragraph.0","strong.0","inlineCode.0"],"code":"`insert(children)`"},{"id":"/root/children/134/children/0/children/0/children/1","type":"text","loc":{"start":24360,"end":24414,"line":{"s":708,"e":708,"code":["- **`insert(children)`** — pipe Fragment-like objects directly into the output"]},"column":{"s":24,"e":78}},"dim":["","list.134","listItem.0","paragraph.0","text.1"],"code":" — pipe Fragment-like objects directly into the output"},{"id":"/root/children/134/children/1","type":"listItem","loc":{"start":24415,"end":24488,"line":{"s":709,"e":709,"code":["- **`inject(text)`** — produce a single raw-body Fragment with no heading"]},"column":{"s":0,"e":73}},"dim":["","list.134","listItem.1"],"code":"- **`inject(text)`** — produce a single raw-body Fragment with no heading"},{"id":"/root/children/134/children/1/children/0","type":"paragraph","loc":{"start":24417,"end":24488,"line":{"s":709,"e":709,"code":["- **`inject(text)`** — produce a single raw-body Fragment with no heading"]},"column":{"s":2,"e":73}},"dim":["","list.134","listItem.1","paragraph.0"],"code":"**`inject(text)`** — produce a single raw-body Fragment with no heading"},{"id":"/root/children/134/children/1/children/0/children/0","type":"strong","loc":{"start":24417,"end":24435,"line":{"s":709,"e":709,"code":["- **`inject(text)`** — produce a single raw-body Fragment with no heading"]},"column":{"s":2,"e":20}},"dim":["","list.134","listItem.1","paragraph.0","strong.0"],"code":"**`inject(text)`**"},{"id":"/root/children/134/children/1/children/0/children/0/children/0","type":"inlineCode","loc":{"start":24419,"end":24433,"line":{"s":709,"e":709,"code":["- **`inject(text)`** — produce a single raw-body Fragment with no heading"]},"column":{"s":4,"e":18}},"dim":["","list.134","listItem.1","paragraph.0","strong.0","inlineCode.0"],"code":"`inject(text)`"},{"id":"/root/children/134/children/1/children/0/children/1","type":"text","loc":{"start":24435,"end":24488,"line":{"s":709,"e":709,"code":["- **`inject(text)`** — produce a single raw-body Fragment with no heading"]},"column":{"s":20,"e":73}},"dim":["","list.134","listItem.1","paragraph.0","text.1"],"code":" — produce a single raw-body Fragment with no heading"},{"id":"/root/children/134/children/2","type":"listItem","loc":{"start":24489,"end":24632,"line":{"s":710,"e":710,"code":["- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)"]},"column":{"s":0,"e":143}},"dim":["","list.134","listItem.2"],"code":"- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)"},{"id":"/root/children/134/children/2/children/0","type":"paragraph","loc":{"start":24491,"end":24632,"line":{"s":710,"e":710,"code":["- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)"]},"column":{"s":2,"e":143}},"dim":["","list.134","listItem.2","paragraph.0"],"code":"**`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)"},{"id":"/root/children/134/children/2/children/0/children/0","type":"strong","loc":{"start":24491,"end":24505,"line":{"s":710,"e":710,"code":["- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)"]},"column":{"s":2,"e":16}},"dim":["","list.134","listItem.2","paragraph.0","strong.0"],"code":"**`children`**"},{"id":"/root/children/134/children/2/children/0/children/0/children/0","type":"inlineCode","loc":{"start":24493,"end":24503,"line":{"s":710,"e":710,"code":["- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)"]},"column":{"s":4,"e":14}},"dim":["","list.134","listItem.2","paragraph.0","strong.0","inlineCode.0"],"code":"`children`"},{"id":"/root/children/134/children/2/children/0/children/1","type":"text","loc":{"start":24505,"end":24632,"line":{"s":710,"e":710,"code":["- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)"]},"column":{"s":16,"e":143}},"dim":["","list.134","listItem.2","paragraph.0","text.1"],"code":" — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)"},{"id":"/root/children/135","type":"heading","loc":{"start":24634,"end":24657,"line":{"s":712,"e":712,"code":["#### `insert(children)`"]},"column":{"s":0,"e":23}},"dim":["","heading.135"],"code":"#### `insert(children)`","symbName":"heading","symbRange":[24659,25299],"symbRangeL":[712,741],"outerCode":"\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```","outerHtml":"\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>"},{"id":"/root/children/135/children/0","type":"inlineCode","loc":{"start":24639,"end":24657,"line":{"s":712,"e":712,"code":["#### `insert(children)`"]},"column":{"s":5,"e":23}},"dim":["","heading.135","inlineCode.0"],"code":"`insert(children)`"},{"id":"/root/children/136","type":"paragraph","loc":{"start":24659,"end":24807,"line":{"s":714,"e":715,"code":["Takes one or more Fragment-like objects and yields each as-is into the output","stream. No wrapping, no heading comment — the caller has full control:"]},"column":{"s":0,"e":70}},"dim":["","paragraph.136"],"code":"Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:"},{"id":"/root/children/136/children/0","type":"text","loc":{"start":24659,"end":24807,"line":{"s":714,"e":715,"code":["Takes one or more Fragment-like objects and yields each as-is into the output","stream. No wrapping, no heading comment — the caller has full control:"]},"column":{"s":0,"e":70}},"dim":["","paragraph.136","text.0"],"code":"Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:"},{"id":"/root/children/137","type":"code","loc":{"start":24810,"end":25160,"line":{"s":718,"e":733,"code":["```","## ${search results}","","\\`\\`\\`javascript","const items = await search(\"mdd\")","return insert(items.map(r => ({","  trail: _mdt_label + \"/\" + r.id,","  heading: \"### \" + r.name,","  headingLevel: 3,","  body: r.description,","  hasChildren: false,","  expand: () => (async function* {})(),","  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,","})))","\\`\\`\\`","```"]},"column":{"s":0,"e":3}},"dim":["","code.137"],"code":"```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```","symbName":"code","symbRange":[25162,25223],"symbRangeL":[null,736],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>"},{"id":"/root/children/138","type":"paragraph","loc":{"start":25162,"end":25223,"line":{"s":735,"e":735,"code":["Pass a single fragment or an array — `insert()` handles both:"]},"column":{"s":0,"e":61}},"dim":["","paragraph.138"],"code":"Pass a single fragment or an array — `insert()` handles both:"},{"id":"/root/children/138/children/0","type":"text","loc":{"start":25162,"end":25199,"line":{"s":735,"e":735,"code":["Pass a single fragment or an array — `insert()` handles both:"]},"column":{"s":0,"e":37}},"dim":["","paragraph.138","text.0"],"code":"Pass a single fragment or an array — "},{"id":"/root/children/138/children/1","type":"inlineCode","loc":{"start":25199,"end":25209,"line":{"s":735,"e":735,"code":["Pass a single fragment or an array — `insert()` handles both:"]},"column":{"s":37,"e":47}},"dim":["","paragraph.138","inlineCode.1"],"code":"`insert()`"},{"id":"/root/children/138/children/2","type":"text","loc":{"start":25209,"end":25223,"line":{"s":735,"e":735,"code":["Pass a single fragment or an array — `insert()` handles both:"]},"column":{"s":47,"e":61}},"dim":["","paragraph.138","text.2"],"code":" handles both:"},{"id":"/root/children/139","type":"code","loc":{"start":25225,"end":25299,"line":{"s":737,"e":740,"code":["```js","return insert(singleFrag);","return insert([fragA, fragB, fragC]);","```"]},"column":{"s":0,"e":3}},"dim":["","code.139"],"code":"```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```","symbName":"code","symbRange":[25301,25413],"symbRangeL":[null,747],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n"},{"id":"/root/children/140","type":"heading","loc":{"start":25301,"end":25320,"line":{"s":742,"e":742,"code":["#### `inject(text)`"]},"column":{"s":0,"e":19}},"dim":["","heading.140"],"code":"#### `inject(text)`","symbName":"heading","symbRange":[25322,25620],"symbRangeL":[742,758],"outerCode":"\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.","outerHtml":"\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>"},{"id":"/root/children/140/children/0","type":"inlineCode","loc":{"start":25306,"end":25320,"line":{"s":742,"e":742,"code":["#### `inject(text)`"]},"column":{"s":5,"e":19}},"dim":["","heading.140","inlineCode.0"],"code":"`inject(text)`"},{"id":"/root/children/141","type":"paragraph","loc":{"start":25322,"end":25413,"line":{"s":744,"e":745,"code":["Takes a string and yields a single raw-body Fragment with no heading, no trail,","no wrapper:"]},"column":{"s":0,"e":11}},"dim":["","paragraph.141"],"code":"Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:"},{"id":"/root/children/141/children/0","type":"text","loc":{"start":25322,"end":25413,"line":{"s":744,"e":745,"code":["Takes a string and yields a single raw-body Fragment with no heading, no trail,","no wrapper:"]},"column":{"s":0,"e":11}},"dim":["","paragraph.141","text.0"],"code":"Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:"},{"id":"/root/children/142","type":"code","loc":{"start":25416,"end":25516,"line":{"s":748,"e":754,"code":["```","## ${notice}","","\\`\\`\\`javascript","return inject(\"> **Note:** generated from live data.\")","\\`\\`\\`","```"]},"column":{"s":0,"e":3}},"dim":["","code.142"],"code":"```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```","symbName":"code","symbRange":[25518,27519],"symbRangeL":[null,782],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n"},{"id":"/root/children/143","type":"paragraph","loc":{"start":25518,"end":25620,"line":{"s":756,"e":757,"code":["The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and","`toString()` returns the raw body."]},"column":{"s":0,"e":34}},"dim":["","paragraph.143"],"code":"The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body."},{"id":"/root/children/143/children/0","type":"text","loc":{"start":25518,"end":25535,"line":{"s":756,"e":756,"code":["The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and"]},"column":{"s":0,"e":17}},"dim":["","paragraph.143","text.0"],"code":"The Fragment has "},{"id":"/root/children/143/children/1","type":"inlineCode","loc":{"start":25535,"end":25548,"line":{"s":756,"e":756,"code":["The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and"]},"column":{"s":17,"e":30}},"dim":["","paragraph.143","inlineCode.1"],"code":"`heading: \"\"`"},{"id":"/root/children/143/children/2","type":"text","loc":{"start":25548,"end":25550,"line":{"s":756,"e":756,"code":["The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and"]},"column":{"s":30,"e":32}},"dim":["","paragraph.143","text.2"],"code":", "},{"id":"/root/children/143/children/3","type":"inlineCode","loc":{"start":25550,"end":25567,"line":{"s":756,"e":756,"code":["The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and"]},"column":{"s":32,"e":49}},"dim":["","paragraph.143","inlineCode.3"],"code":"`headingLevel: 0`"},{"id":"/root/children/143/children/4","type":"text","loc":{"start":25567,"end":25569,"line":{"s":756,"e":756,"code":["The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and"]},"column":{"s":49,"e":51}},"dim":["","paragraph.143","text.4"],"code":", "},{"id":"/root/children/143/children/5","type":"inlineCode","loc":{"start":25569,"end":25580,"line":{"s":756,"e":756,"code":["The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and"]},"column":{"s":51,"e":62}},"dim":["","paragraph.143","inlineCode.5"],"code":"`trail: \"\"`"},{"id":"/root/children/143/children/6","type":"text","loc":{"start":25580,"end":25586,"line":{"s":756,"e":757,"code":["The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and","`toString()` returns the raw body."]},"column":{"s":62,"e":0}},"dim":["","paragraph.143","text.6"],"code":", and\n"},{"id":"/root/children/143/children/7","type":"inlineCode","loc":{"start":25586,"end":25598,"line":{"s":757,"e":757,"code":["`toString()` returns the raw body."]},"column":{"s":0,"e":12}},"dim":["","paragraph.143","inlineCode.7"],"code":"`toString()`"},{"id":"/root/children/143/children/8","type":"text","loc":{"start":25598,"end":25620,"line":{"s":757,"e":757,"code":["`toString()` returns the raw body."]},"column":{"s":12,"e":34}},"dim":["","paragraph.143","text.8"],"code":" returns the raw body."},{"id":"/root/children/144","type":"heading","loc":{"start":25622,"end":25674,"line":{"s":759,"e":759,"code":["#### `children` — recursively resolved child subtree"]},"column":{"s":0,"e":52}},"dim":["","heading.144"],"code":"#### `children` — recursively resolved child subtree","symbName":"heading","symbRange":[25676,27987],"symbRangeL":[759,799],"outerCode":"\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.","outerHtml":"\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>"},{"id":"/root/children/144/children/0","type":"inlineCode","loc":{"start":25627,"end":25637,"line":{"s":759,"e":759,"code":["#### `children` — recursively resolved child subtree"]},"column":{"s":5,"e":15}},"dim":["","heading.144","inlineCode.0"],"code":"`children`"},{"id":"/root/children/144/children/1","type":"text","loc":{"start":25637,"end":25674,"line":{"s":759,"e":759,"code":["#### `children` — recursively resolved child subtree"]},"column":{"s":15,"e":52}},"dim":["","heading.144","text.1"],"code":" — recursively resolved child subtree"},{"id":"/root/children/145","type":"paragraph","loc":{"start":25676,"end":25963,"line":{"s":761,"e":764,"code":["The `children` variable holds the resolved output of the extruction's child","subtree — all headings between this extruction and the next heading at the","same or higher depth. Non-heading body text after the extruction heading is","**not** included (that's the `bodyText` passed to `evalFn`)."]},"column":{"s":0,"e":60}},"dim":["","paragraph.145"],"code":"The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`)."},{"id":"/root/children/145/children/0","type":"text","loc":{"start":25676,"end":25680,"line":{"s":761,"e":761,"code":["The `children` variable holds the resolved output of the extruction's child"]},"column":{"s":0,"e":4}},"dim":["","paragraph.145","text.0"],"code":"The "},{"id":"/root/children/145/children/1","type":"inlineCode","loc":{"start":25680,"end":25690,"line":{"s":761,"e":761,"code":["The `children` variable holds the resolved output of the extruction's child"]},"column":{"s":4,"e":14}},"dim":["","paragraph.145","inlineCode.1"],"code":"`children`"},{"id":"/root/children/145/children/2","type":"text","loc":{"start":25690,"end":25903,"line":{"s":761,"e":764,"code":["The `children` variable holds the resolved output of the extruction's child","subtree — all headings between this extruction and the next heading at the","same or higher depth. Non-heading body text after the extruction heading is","**not** included (that's the `bodyText` passed to `evalFn`)."]},"column":{"s":14,"e":0}},"dim":["","paragraph.145","text.2"],"code":" variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n"},{"id":"/root/children/145/children/3","type":"strong","loc":{"start":25903,"end":25910,"line":{"s":764,"e":764,"code":["**not** included (that's the `bodyText` passed to `evalFn`)."]},"column":{"s":0,"e":7}},"dim":["","paragraph.145","strong.3"],"code":"**not**"},{"id":"/root/children/145/children/3/children/0","type":"text","loc":{"start":25905,"end":25908,"line":{"s":764,"e":764,"code":["**not** included (that's the `bodyText` passed to `evalFn`)."]},"column":{"s":2,"e":5}},"dim":["","paragraph.145","strong.3","text.0"],"code":"not"},{"id":"/root/children/145/children/4","type":"text","loc":{"start":25910,"end":25932,"line":{"s":764,"e":764,"code":["**not** included (that's the `bodyText` passed to `evalFn`)."]},"column":{"s":7,"e":29}},"dim":["","paragraph.145","text.4"],"code":" included (that's the "},{"id":"/root/children/145/children/5","type":"inlineCode","loc":{"start":25932,"end":25942,"line":{"s":764,"e":764,"code":["**not** included (that's the `bodyText` passed to `evalFn`)."]},"column":{"s":29,"e":39}},"dim":["","paragraph.145","inlineCode.5"],"code":"`bodyText`"},{"id":"/root/children/145/children/6","type":"text","loc":{"start":25942,"end":25953,"line":{"s":764,"e":764,"code":["**not** included (that's the `bodyText` passed to `evalFn`)."]},"column":{"s":39,"e":50}},"dim":["","paragraph.145","text.6"],"code":" passed to "},{"id":"/root/children/145/children/7","type":"inlineCode","loc":{"start":25953,"end":25961,"line":{"s":764,"e":764,"code":["**not** included (that's the `bodyText` passed to `evalFn`)."]},"column":{"s":50,"e":58}},"dim":["","paragraph.145","inlineCode.7"],"code":"`evalFn`"},{"id":"/root/children/145/children/8","type":"text","loc":{"start":25961,"end":25963,"line":{"s":764,"e":764,"code":["**not** included (that's the `bodyText` passed to `evalFn`)."]},"column":{"s":58,"e":60}},"dim":["","paragraph.145","text.8"],"code":")."},{"id":"/root/children/146","type":"paragraph","loc":{"start":25965,"end":26069,"line":{"s":766,"e":767,"code":["Resolution is **recursive** — `children` is computed by walking the child","tree and processing each node:"]},"column":{"s":0,"e":30}},"dim":["","paragraph.146"],"code":"Resolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:"},{"id":"/root/children/146/children/0","type":"text","loc":{"start":25965,"end":25979,"line":{"s":766,"e":766,"code":["Resolution is **recursive** — `children` is computed by walking the child"]},"column":{"s":0,"e":14}},"dim":["","paragraph.146","text.0"],"code":"Resolution is "},{"id":"/root/children/146/children/1","type":"strong","loc":{"start":25979,"end":25992,"line":{"s":766,"e":766,"code":["Resolution is **recursive** — `children` is computed by walking the child"]},"column":{"s":14,"e":27}},"dim":["","paragraph.146","strong.1"],"code":"**recursive**"},{"id":"/root/children/146/children/1/children/0","type":"text","loc":{"start":25981,"end":25990,"line":{"s":766,"e":766,"code":["Resolution is **recursive** — `children` is computed by walking the child"]},"column":{"s":16,"e":25}},"dim":["","paragraph.146","strong.1","text.0"],"code":"recursive"},{"id":"/root/children/146/children/2","type":"text","loc":{"start":25992,"end":25995,"line":{"s":766,"e":766,"code":["Resolution is **recursive** — `children` is computed by walking the child"]},"column":{"s":27,"e":30}},"dim":["","paragraph.146","text.2"],"code":" — "},{"id":"/root/children/146/children/3","type":"inlineCode","loc":{"start":25995,"end":26005,"line":{"s":766,"e":766,"code":["Resolution is **recursive** — `children` is computed by walking the child"]},"column":{"s":30,"e":40}},"dim":["","paragraph.146","inlineCode.3"],"code":"`children`"},{"id":"/root/children/146/children/4","type":"text","loc":{"start":26005,"end":26069,"line":{"s":766,"e":767,"code":["Resolution is **recursive** — `children` is computed by walking the child","tree and processing each node:"]},"column":{"s":40,"e":30}},"dim":["","paragraph.146","text.4"],"code":" is computed by walking the child\ntree and processing each node:"},{"id":"/root/children/147","type":"paragraph","loc":{"start":26071,"end":27309,"line":{"s":769,"e":775,"code":["| Child type                                           | Treatment                                                                                                             |","| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |","| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |","| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |","| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |","| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |","| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |"]},"column":{"s":0,"e":176}},"dim":["","paragraph.147"],"code":"| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |"},{"id":"/root/children/147/children/0","type":"text","loc":{"start":26071,"end":26427,"line":{"s":769,"e":771,"code":["| Child type                                           | Treatment                                                                                                             |","| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |","| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.147","text.0"],"code":"| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| "},{"id":"/root/children/147/children/1","type":"strong","loc":{"start":26427,"end":26441,"line":{"s":771,"e":771,"code":["| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |"]},"column":{"s":2,"e":16}},"dim":["","paragraph.147","strong.1"],"code":"**Extruction**"},{"id":"/root/children/147/children/1/children/0","type":"text","loc":{"start":26429,"end":26439,"line":{"s":771,"e":771,"code":["| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |"]},"column":{"s":4,"e":14}},"dim":["","paragraph.147","strong.1","text.0"],"code":"Extruction"},{"id":"/root/children/147/children/2","type":"text","loc":{"start":26441,"end":26515,"line":{"s":771,"e":771,"code":["| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |"]},"column":{"s":16,"e":90}},"dim":["","paragraph.147","text.2"],"code":" (with result)                         | Evaluated with its own recursive "},{"id":"/root/children/147/children/3","type":"inlineCode","loc":{"start":26515,"end":26525,"line":{"s":771,"e":771,"code":["| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |"]},"column":{"s":90,"e":100}},"dim":["","paragraph.147","inlineCode.3"],"code":"`children`"},{"id":"/root/children/147/children/4","type":"text","loc":{"start":26525,"end":26539,"line":{"s":771,"e":771,"code":["| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |"]},"column":{"s":100,"e":114}},"dim":["","paragraph.147","text.4"],"code":"; its output ("},{"id":"/root/children/147/children/5","type":"inlineCode","loc":{"start":26539,"end":26547,"line":{"s":771,"e":771,"code":["| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |"]},"column":{"s":114,"e":122}},"dim":["","paragraph.147","inlineCode.5"],"code":"`inject`"},{"id":"/root/children/147/children/6","type":"text","loc":{"start":26547,"end":26548,"line":{"s":771,"e":771,"code":["| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |"]},"column":{"s":122,"e":123}},"dim":["","paragraph.147","text.6"],"code":"/"},{"id":"/root/children/147/children/7","type":"inlineCode","loc":{"start":26548,"end":26556,"line":{"s":771,"e":771,"code":["| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |"]},"column":{"s":123,"e":131}},"dim":["","paragraph.147","inlineCode.7"],"code":"`insert`"},{"id":"/root/children/147/children/8","type":"text","loc":{"start":26556,"end":26604,"line":{"s":771,"e":772,"code":["| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |","| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |"]},"column":{"s":131,"e":2}},"dim":["","paragraph.147","text.8"],"code":" bodies) is stringified and included        |\n| "},{"id":"/root/children/147/children/9","type":"strong","loc":{"start":26604,"end":26618,"line":{"s":772,"e":772,"code":["| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |"]},"column":{"s":2,"e":16}},"dim":["","paragraph.147","strong.9"],"code":"**Extruction**"},{"id":"/root/children/147/children/9/children/0","type":"text","loc":{"start":26606,"end":26616,"line":{"s":772,"e":772,"code":["| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |"]},"column":{"s":4,"e":14}},"dim":["","paragraph.147","strong.9","text.0"],"code":"Extruction"},{"id":"/root/children/147/children/10","type":"text","loc":{"start":26618,"end":26634,"line":{"s":772,"e":772,"code":["| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |"]},"column":{"s":16,"e":32}},"dim":["","paragraph.147","text.10"],"code":" (transparent — "},{"id":"/root/children/147/children/11","type":"inlineCode","loc":{"start":26634,"end":26645,"line":{"s":772,"e":772,"code":["| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |"]},"column":{"s":32,"e":43}},"dim":["","paragraph.147","inlineCode.11"],"code":"`undefined`"},{"id":"/root/children/147/children/12","type":"text","loc":{"start":26645,"end":26781,"line":{"s":772,"e":773,"code":["| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |","| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |"]},"column":{"s":43,"e":2}},"dim":["","paragraph.147","text.12"],"code":"/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| "},{"id":"/root/children/147/children/13","type":"strong","loc":{"start":26781,"end":26795,"line":{"s":773,"e":773,"code":["| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |"]},"column":{"s":2,"e":16}},"dim":["","paragraph.147","strong.13"],"code":"**Extruction**"},{"id":"/root/children/147/children/13/children/0","type":"text","loc":{"start":26783,"end":26793,"line":{"s":773,"e":773,"code":["| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |"]},"column":{"s":4,"e":14}},"dim":["","paragraph.147","strong.13","text.0"],"code":"Extruction"},{"id":"/root/children/147/children/14","type":"text","loc":{"start":26795,"end":26810,"line":{"s":773,"e":773,"code":["| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |"]},"column":{"s":16,"e":31}},"dim":["","paragraph.147","text.14"],"code":" (suppressed — "},{"id":"/root/children/147/children/15","type":"inlineCode","loc":{"start":26810,"end":26816,"line":{"s":773,"e":773,"code":["| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |"]},"column":{"s":31,"e":37}},"dim":["","paragraph.147","inlineCode.15"],"code":"`null`"},{"id":"/root/children/147/children/16","type":"text","loc":{"start":26816,"end":26896,"line":{"s":773,"e":773,"code":["| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |"]},"column":{"s":37,"e":117}},"dim":["","paragraph.147","text.16"],"code":")                 | Entire subtree dropped — children do not appear in parent's "},{"id":"/root/children/147/children/17","type":"inlineCode","loc":{"start":26896,"end":26906,"line":{"s":773,"e":773,"code":["| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |"]},"column":{"s":117,"e":127}},"dim":["","paragraph.147","inlineCode.17"],"code":"`children`"},{"id":"/root/children/147/children/18","type":"text","loc":{"start":26906,"end":26958,"line":{"s":773,"e":774,"code":["| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |","| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |"]},"column":{"s":127,"e":2}},"dim":["","paragraph.147","text.18"],"code":"                                                |\n| "},{"id":"/root/children/147/children/19","type":"strong","loc":{"start":26958,"end":26972,"line":{"s":774,"e":774,"code":["| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |"]},"column":{"s":2,"e":16}},"dim":["","paragraph.147","strong.19"],"code":"**Extruction**"},{"id":"/root/children/147/children/19/children/0","type":"text","loc":{"start":26960,"end":26970,"line":{"s":774,"e":774,"code":["| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |"]},"column":{"s":4,"e":14}},"dim":["","paragraph.147","strong.19","text.0"],"code":"Extruction"},{"id":"/root/children/147/children/20","type":"text","loc":{"start":26972,"end":26988,"line":{"s":774,"e":774,"code":["| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |"]},"column":{"s":16,"e":32}},"dim":["","paragraph.147","text.20"],"code":" (errored, with "},{"id":"/root/children/147/children/21","type":"inlineCode","loc":{"start":26988,"end":27007,"line":{"s":774,"e":774,"code":["| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |"]},"column":{"s":32,"e":51}},"dim":["","paragraph.147","inlineCode.21"],"code":"`onExtructionError`"},{"id":"/root/children/147/children/22","type":"text","loc":{"start":27007,"end":27073,"line":{"s":774,"e":774,"code":["| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |"]},"column":{"s":51,"e":117}},"dim":["","paragraph.147","text.22"],"code":")   | Caught; treated as transparent — children promoted (same as "},{"id":"/root/children/147/children/23","type":"inlineCode","loc":{"start":27073,"end":27093,"line":{"s":774,"e":774,"code":["| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |"]},"column":{"s":117,"e":137}},"dim":["","paragraph.147","inlineCode.23"],"code":"`skipExtructionBody`"},{"id":"/root/children/147/children/24","type":"text","loc":{"start":27093,"end":27135,"line":{"s":774,"e":775,"code":["| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |","| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |"]},"column":{"s":137,"e":2}},"dim":["","paragraph.147","text.24"],"code":")                                     |\n| "},{"id":"/root/children/147/children/25","type":"strong","loc":{"start":27135,"end":27154,"line":{"s":775,"e":775,"code":["| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |"]},"column":{"s":2,"e":21}},"dim":["","paragraph.147","strong.25"],"code":"**Regular heading**"},{"id":"/root/children/147/children/25/children/0","type":"text","loc":{"start":27137,"end":27152,"line":{"s":775,"e":775,"code":["| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |"]},"column":{"s":4,"e":19}},"dim":["","paragraph.147","strong.25","text.0"],"code":"Regular heading"},{"id":"/root/children/147/children/26","type":"text","loc":{"start":27154,"end":27309,"line":{"s":775,"e":775,"code":["| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |"]},"column":{"s":21,"e":176}},"dim":["","paragraph.147","text.26"],"code":"                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |"},{"id":"/root/children/148","type":"paragraph","loc":{"start":27311,"end":27461,"line":{"s":777,"e":778,"code":["This means extructions at any depth are fully evaluated — a `##### ${...}`","deep under a regular `####` heading will still produce its resolved output."]},"column":{"s":0,"e":75}},"dim":["","paragraph.148"],"code":"This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output."},{"id":"/root/children/148/children/0","type":"text","loc":{"start":27311,"end":27371,"line":{"s":777,"e":777,"code":["This means extructions at any depth are fully evaluated — a `##### ${...}`"]},"column":{"s":0,"e":60}},"dim":["","paragraph.148","text.0"],"code":"This means extructions at any depth are fully evaluated — a "},{"id":"/root/children/148/children/1","type":"inlineCode","loc":{"start":27371,"end":27385,"line":{"s":777,"e":777,"code":["This means extructions at any depth are fully evaluated — a `##### ${...}`"]},"column":{"s":60,"e":74}},"dim":["","paragraph.148","inlineCode.1"],"code":"`##### ${...}`"},{"id":"/root/children/148/children/2","type":"text","loc":{"start":27385,"end":27407,"line":{"s":777,"e":778,"code":["This means extructions at any depth are fully evaluated — a `##### ${...}`","deep under a regular `####` heading will still produce its resolved output."]},"column":{"s":74,"e":21}},"dim":["","paragraph.148","text.2"],"code":"\ndeep under a regular "},{"id":"/root/children/148/children/3","type":"inlineCode","loc":{"start":27407,"end":27413,"line":{"s":778,"e":778,"code":["deep under a regular `####` heading will still produce its resolved output."]},"column":{"s":21,"e":27}},"dim":["","paragraph.148","inlineCode.3"],"code":"`####`"},{"id":"/root/children/148/children/4","type":"text","loc":{"start":27413,"end":27461,"line":{"s":778,"e":778,"code":["deep under a regular `####` heading will still produce its resolved output."]},"column":{"s":27,"e":75}},"dim":["","paragraph.148","text.4"],"code":" heading will still produce its resolved output."},{"id":"/root/children/149","type":"paragraph","loc":{"start":27463,"end":27519,"line":{"s":780,"e":780,"code":["A common pattern is to pipe children through `insert()`:"]},"column":{"s":0,"e":56}},"dim":["","paragraph.149"],"code":"A common pattern is to pipe children through `insert()`:"},{"id":"/root/children/149/children/0","type":"text","loc":{"start":27463,"end":27508,"line":{"s":780,"e":780,"code":["A common pattern is to pipe children through `insert()`:"]},"column":{"s":0,"e":45}},"dim":["","paragraph.149","text.0"],"code":"A common pattern is to pipe children through "},{"id":"/root/children/149/children/1","type":"inlineCode","loc":{"start":27508,"end":27518,"line":{"s":780,"e":780,"code":["A common pattern is to pipe children through `insert()`:"]},"column":{"s":45,"e":55}},"dim":["","paragraph.149","inlineCode.1"],"code":"`insert()`"},{"id":"/root/children/149/children/2","type":"text","loc":{"start":27518,"end":27519,"line":{"s":780,"e":780,"code":["A common pattern is to pipe children through `insert()`:"]},"column":{"s":55,"e":56}},"dim":["","paragraph.149","text.2"],"code":":"},{"id":"/root/children/150","type":"code","loc":{"start":27522,"end":27633,"line":{"s":783,"e":789,"code":["```","## ${list of todos}","","\\`\\`\\`javascript","return [inject(\"> Generated list:\\n\\n\"), insert(children)]","\\`\\`\\`","```"]},"column":{"s":0,"e":3}},"dim":["","code.150"],"code":"```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```","symbName":"code","symbRange":[27635,28162],"symbRangeL":[null,805],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n"},{"id":"/root/children/151","type":"paragraph","loc":{"start":27635,"end":27675,"line":{"s":791,"e":791,"code":["`children` is an empty string `\"\"` when:"]},"column":{"s":0,"e":40}},"dim":["","paragraph.151"],"code":"`children` is an empty string `\"\"` when:"},{"id":"/root/children/151/children/0","type":"inlineCode","loc":{"start":27635,"end":27645,"line":{"s":791,"e":791,"code":["`children` is an empty string `\"\"` when:"]},"column":{"s":0,"e":10}},"dim":["","paragraph.151","inlineCode.0"],"code":"`children`"},{"id":"/root/children/151/children/1","type":"text","loc":{"start":27645,"end":27665,"line":{"s":791,"e":791,"code":["`children` is an empty string `\"\"` when:"]},"column":{"s":10,"e":30}},"dim":["","paragraph.151","text.1"],"code":" is an empty string "},{"id":"/root/children/151/children/2","type":"inlineCode","loc":{"start":27665,"end":27669,"line":{"s":791,"e":791,"code":["`children` is an empty string `\"\"` when:"]},"column":{"s":30,"e":34}},"dim":["","paragraph.151","inlineCode.2"],"code":"`\"\"`"},{"id":"/root/children/151/children/3","type":"text","loc":{"start":27669,"end":27675,"line":{"s":791,"e":791,"code":["`children` is an empty string `\"\"` when:"]},"column":{"s":34,"e":40}},"dim":["","paragraph.151","text.3"],"code":" when:"},{"id":"/root/children/152","type":"list","loc":{"start":27677,"end":27766,"line":{"s":793,"e":794,"code":["- The extruction has no child headings","- The extruction is at root level with no children"]},"column":{"s":0,"e":50}},"dim":["","list.152"],"code":"- The extruction has no child headings\n- The extruction is at root level with no children","symbName":"list","symbRange":[27768,36303],"symbRangeL":[793,1007],"outerCode":"- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing","outerHtml":"<ul><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>"},{"id":"/root/children/152/children/0","type":"listItem","loc":{"start":27677,"end":27715,"line":{"s":793,"e":793,"code":["- The extruction has no child headings"]},"column":{"s":0,"e":38}},"dim":["","list.152","listItem.0"],"code":"- The extruction has no child headings"},{"id":"/root/children/152/children/0/children/0","type":"paragraph","loc":{"start":27679,"end":27715,"line":{"s":793,"e":793,"code":["- The extruction has no child headings"]},"column":{"s":2,"e":38}},"dim":["","list.152","listItem.0","paragraph.0"],"code":"The extruction has no child headings"},{"id":"/root/children/152/children/0/children/0/children/0","type":"text","loc":{"start":27679,"end":27715,"line":{"s":793,"e":793,"code":["- The extruction has no child headings"]},"column":{"s":2,"e":38}},"dim":["","list.152","listItem.0","paragraph.0","text.0"],"code":"The extruction has no child headings"},{"id":"/root/children/152/children/1","type":"listItem","loc":{"start":27716,"end":27766,"line":{"s":794,"e":794,"code":["- The extruction is at root level with no children"]},"column":{"s":0,"e":50}},"dim":["","list.152","listItem.1"],"code":"- The extruction is at root level with no children"},{"id":"/root/children/152/children/1/children/0","type":"paragraph","loc":{"start":27718,"end":27766,"line":{"s":794,"e":794,"code":["- The extruction is at root level with no children"]},"column":{"s":2,"e":50}},"dim":["","list.152","listItem.1","paragraph.0"],"code":"The extruction is at root level with no children"},{"id":"/root/children/152/children/1/children/0/children/0","type":"text","loc":{"start":27718,"end":27766,"line":{"s":794,"e":794,"code":["- The extruction is at root level with no children"]},"column":{"s":2,"e":50}},"dim":["","list.152","listItem.1","paragraph.0","text.0"],"code":"The extruction is at root level with no children"},{"id":"/root/children/153","type":"paragraph","loc":{"start":27768,"end":27987,"line":{"s":796,"e":798,"code":["Non-extruction headings are included as original markdown (source positions","preserve formatting). Extruction headings themselves never appear in the","output — they're transparent, only their resolved content is included."]},"column":{"s":0,"e":70}},"dim":["","paragraph.153"],"code":"Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included."},{"id":"/root/children/153/children/0","type":"text","loc":{"start":27768,"end":27987,"line":{"s":796,"e":798,"code":["Non-extruction headings are included as original markdown (source positions","preserve formatting). Extruction headings themselves never appear in the","output — they're transparent, only their resolved content is included."]},"column":{"s":0,"e":70}},"dim":["","paragraph.153","text.0"],"code":"Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included."},{"id":"/root/children/154","type":"heading","loc":{"start":27989,"end":28029,"line":{"s":800,"e":800,"code":["#### `insertRefsAsSubtree(items, opts?)`"]},"column":{"s":0,"e":40}},"dim":["","heading.154"],"code":"#### `insertRefsAsSubtree(items, opts?)`","symbName":"heading","symbRange":[28031,31633],"symbRangeL":[800,857],"outerCode":"\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.","outerHtml":"\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>"},{"id":"/root/children/154/children/0","type":"inlineCode","loc":{"start":27994,"end":28029,"line":{"s":800,"e":800,"code":["#### `insertRefsAsSubtree(items, opts?)`"]},"column":{"s":5,"e":40}},"dim":["","heading.154","inlineCode.0"],"code":"`insertRefsAsSubtree(items, opts?)`"},{"id":"/root/children/155","type":"paragraph","loc":{"start":28031,"end":28162,"line":{"s":802,"e":803,"code":["Turn an array of fragment refs (typically `await search(...)` results) into","child-depth heading Fragments with **lazy body-fetch**:"]},"column":{"s":0,"e":55}},"dim":["","paragraph.155"],"code":"Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:"},{"id":"/root/children/155/children/0","type":"text","loc":{"start":28031,"end":28073,"line":{"s":802,"e":802,"code":["Turn an array of fragment refs (typically `await search(...)` results) into"]},"column":{"s":0,"e":42}},"dim":["","paragraph.155","text.0"],"code":"Turn an array of fragment refs (typically "},{"id":"/root/children/155/children/1","type":"inlineCode","loc":{"start":28073,"end":28092,"line":{"s":802,"e":802,"code":["Turn an array of fragment refs (typically `await search(...)` results) into"]},"column":{"s":42,"e":61}},"dim":["","paragraph.155","inlineCode.1"],"code":"`await search(...)`"},{"id":"/root/children/155/children/2","type":"text","loc":{"start":28092,"end":28142,"line":{"s":802,"e":803,"code":["Turn an array of fragment refs (typically `await search(...)` results) into","child-depth heading Fragments with **lazy body-fetch**:"]},"column":{"s":61,"e":35}},"dim":["","paragraph.155","text.2"],"code":" results) into\nchild-depth heading Fragments with "},{"id":"/root/children/155/children/3","type":"strong","loc":{"start":28142,"end":28161,"line":{"s":803,"e":803,"code":["child-depth heading Fragments with **lazy body-fetch**:"]},"column":{"s":35,"e":54}},"dim":["","paragraph.155","strong.3"],"code":"**lazy body-fetch**"},{"id":"/root/children/155/children/3/children/0","type":"text","loc":{"start":28144,"end":28159,"line":{"s":803,"e":803,"code":["child-depth heading Fragments with **lazy body-fetch**:"]},"column":{"s":37,"e":52}},"dim":["","paragraph.155","strong.3","text.0"],"code":"lazy body-fetch"},{"id":"/root/children/155/children/4","type":"text","loc":{"start":28161,"end":28162,"line":{"s":803,"e":803,"code":["child-depth heading Fragments with **lazy body-fetch**:"]},"column":{"s":54,"e":55}},"dim":["","paragraph.155","text.4"],"code":":"},{"id":"/root/children/156","type":"code","loc":{"start":28165,"end":28279,"line":{"s":806,"e":812,"code":["```","## ${search fragments; do}","","\\`\\`\\`javascript","return [insertRefsAsSubtree(await search(_mdt_label))]","\\`\\`\\`","```"]},"column":{"s":0,"e":3}},"dim":["","code.156"],"code":"```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```","symbName":"code","symbRange":[28281,28635],"symbRangeL":[null,820],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n"},{"id":"/root/children/157","type":"paragraph","loc":{"start":28281,"end":28635,"line":{"s":814,"e":818,"code":["Each item becomes ONE Fragment one level **below** the extruction","(`extruction.depth + 1`), so the results nest as children of the current","level. The Fragment's body is empty at yield-time; the fetch happens only","inside its `expand()` — i.e. only when the render pipeline walks into that","subtree. Depth is clamped at 6 (markdown's maximum heading level)."]},"column":{"s":0,"e":66}},"dim":["","paragraph.157"],"code":"Each item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level)."},{"id":"/root/children/157/children/0","type":"text","loc":{"start":28281,"end":28322,"line":{"s":814,"e":814,"code":["Each item becomes ONE Fragment one level **below** the extruction"]},"column":{"s":0,"e":41}},"dim":["","paragraph.157","text.0"],"code":"Each item becomes ONE Fragment one level "},{"id":"/root/children/157/children/1","type":"strong","loc":{"start":28322,"end":28331,"line":{"s":814,"e":814,"code":["Each item becomes ONE Fragment one level **below** the extruction"]},"column":{"s":41,"e":50}},"dim":["","paragraph.157","strong.1"],"code":"**below**"},{"id":"/root/children/157/children/1/children/0","type":"text","loc":{"start":28324,"end":28329,"line":{"s":814,"e":814,"code":["Each item becomes ONE Fragment one level **below** the extruction"]},"column":{"s":43,"e":48}},"dim":["","paragraph.157","strong.1","text.0"],"code":"below"},{"id":"/root/children/157/children/2","type":"text","loc":{"start":28331,"end":28348,"line":{"s":814,"e":815,"code":["Each item becomes ONE Fragment one level **below** the extruction","(`extruction.depth + 1`), so the results nest as children of the current"]},"column":{"s":50,"e":1}},"dim":["","paragraph.157","text.2"],"code":" the extruction\n("},{"id":"/root/children/157/children/3","type":"inlineCode","loc":{"start":28348,"end":28370,"line":{"s":815,"e":815,"code":["(`extruction.depth + 1`), so the results nest as children of the current"]},"column":{"s":1,"e":23}},"dim":["","paragraph.157","inlineCode.3"],"code":"`extruction.depth + 1`"},{"id":"/root/children/157/children/4","type":"text","loc":{"start":28370,"end":28505,"line":{"s":815,"e":817,"code":["(`extruction.depth + 1`), so the results nest as children of the current","level. The Fragment's body is empty at yield-time; the fetch happens only","inside its `expand()` — i.e. only when the render pipeline walks into that"]},"column":{"s":23,"e":11}},"dim":["","paragraph.157","text.4"],"code":"), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its "},{"id":"/root/children/157/children/5","type":"inlineCode","loc":{"start":28505,"end":28515,"line":{"s":817,"e":817,"code":["inside its `expand()` — i.e. only when the render pipeline walks into that"]},"column":{"s":11,"e":21}},"dim":["","paragraph.157","inlineCode.5"],"code":"`expand()`"},{"id":"/root/children/157/children/6","type":"text","loc":{"start":28515,"end":28635,"line":{"s":817,"e":818,"code":["inside its `expand()` — i.e. only when the render pipeline walks into that","subtree. Depth is clamped at 6 (markdown's maximum heading level)."]},"column":{"s":21,"e":66}},"dim":["","paragraph.157","text.6"],"code":" — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level)."},{"id":"/root/children/158","type":"code","loc":{"start":28638,"end":28890,"line":{"s":821,"e":826,"code":["```","## insertRefsAsSubtree      ← depth 2, visible parent","### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)","#### auth                   ← depth 4, one Fragment per item","##### …transcluded body…    ← depth 5+, from loadRefBody","```"]},"column":{"s":0,"e":3}},"dim":["","code.158"],"code":"```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```","symbName":"code","symbRange":[28892,31781],"symbRangeL":[null,863],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n"},{"id":"/root/children/159","type":"paragraph","loc":{"start":28892,"end":29021,"line":{"s":828,"e":829,"code":["This is the only verb whose heading is real markdown — every other verb","emits an HTML-comment heading, so its depth is invisible."]},"column":{"s":0,"e":57}},"dim":["","paragraph.159"],"code":"This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible."},{"id":"/root/children/159/children/0","type":"text","loc":{"start":28892,"end":29021,"line":{"s":828,"e":829,"code":["This is the only verb whose heading is real markdown — every other verb","emits an HTML-comment heading, so its depth is invisible."]},"column":{"s":0,"e":57}},"dim":["","paragraph.159","text.0"],"code":"This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible."},{"id":"/root/children/160","type":"paragraph","loc":{"start":29023,"end":29051,"line":{"s":831,"e":831,"code":["**Item contract (minimum):**"]},"column":{"s":0,"e":28}},"dim":["","paragraph.160"],"code":"**Item contract (minimum):**"},{"id":"/root/children/160/children/0","type":"strong","loc":{"start":29023,"end":29051,"line":{"s":831,"e":831,"code":["**Item contract (minimum):**"]},"column":{"s":0,"e":28}},"dim":["","paragraph.160","strong.0"],"code":"**Item contract (minimum):**"},{"id":"/root/children/160/children/0/children/0","type":"text","loc":{"start":29025,"end":29049,"line":{"s":831,"e":831,"code":["**Item contract (minimum):**"]},"column":{"s":2,"e":26}},"dim":["","paragraph.160","strong.0","text.0"],"code":"Item contract (minimum):"},{"id":"/root/children/161","type":"paragraph","loc":{"start":29053,"end":30804,"line":{"s":833,"e":838,"code":["| Field                              | Purpose                                                                                                                                                                                                                                                    |","| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |","| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |","| `fn`                               | Source file path                                                                                                                                                                                                                                           |","| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |","| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |"]},"column":{"s":0,"e":291}},"dim":["","paragraph.161"],"code":"| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |"},{"id":"/root/children/161/children/0","type":"text","loc":{"start":29053,"end":29639,"line":{"s":833,"e":835,"code":["| Field                              | Purpose                                                                                                                                                                                                                                                    |","| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |","| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.161","text.0"],"code":"| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| "},{"id":"/root/children/161/children/1","type":"inlineCode","loc":{"start":29639,"end":29646,"line":{"s":835,"e":835,"code":["| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |"]},"column":{"s":2,"e":9}},"dim":["","paragraph.161","inlineCode.1"],"code":"`nomen`"},{"id":"/root/children/161/children/2","type":"text","loc":{"start":29646,"end":29649,"line":{"s":835,"e":835,"code":["| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |"]},"column":{"s":9,"e":12}},"dim":["","paragraph.161","text.2"],"code":" / "},{"id":"/root/children/161/children/3","type":"inlineCode","loc":{"start":29649,"end":29654,"line":{"s":835,"e":835,"code":["| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |"]},"column":{"s":12,"e":17}},"dim":["","paragraph.161","inlineCode.3"],"code":"`ref`"},{"id":"/root/children/161/children/4","type":"text","loc":{"start":29654,"end":29657,"line":{"s":835,"e":835,"code":["| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |"]},"column":{"s":17,"e":20}},"dim":["","paragraph.161","text.4"],"code":" / "},{"id":"/root/children/161/children/5","type":"inlineCode","loc":{"start":29657,"end":29664,"line":{"s":835,"e":835,"code":["| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |"]},"column":{"s":20,"e":27}},"dim":["","paragraph.161","inlineCode.5"],"code":"`trail`"},{"id":"/root/children/161/children/6","type":"text","loc":{"start":29664,"end":29667,"line":{"s":835,"e":835,"code":["| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |"]},"column":{"s":27,"e":30}},"dim":["","paragraph.161","text.6"],"code":" / "},{"id":"/root/children/161/children/7","type":"inlineCode","loc":{"start":29667,"end":29673,"line":{"s":835,"e":835,"code":["| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |"]},"column":{"s":30,"e":36}},"dim":["","paragraph.161","inlineCode.7"],"code":"`name`"},{"id":"/root/children/161/children/8","type":"text","loc":{"start":29673,"end":29710,"line":{"s":835,"e":835,"code":["| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |"]},"column":{"s":36,"e":73}},"dim":["","paragraph.161","text.8"],"code":" | Heading text — resolves in order: "},{"id":"/root/children/161/children/9","type":"inlineCode","loc":{"start":29710,"end":29717,"line":{"s":835,"e":835,"code":["| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |"]},"column":{"s":73,"e":80}},"dim":["","paragraph.161","inlineCode.9"],"code":"`nomen`"},{"id":"/root/children/161/children/10","type":"text","loc":{"start":29717,"end":29735,"line":{"s":835,"e":835,"code":["| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |"]},"column":{"s":80,"e":98}},"dim":["","paragraph.161","text.10"],"code":" (pre-computed) → "},{"id":"/root/children/161/children/11","type":"inlineCode","loc":{"start":29735,"end":29758,"line":{"s":835,"e":835,"code":["| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |"]},"column":{"s":98,"e":121}},"dim":["","paragraph.161","inlineCode.11"],"code":"`ref.split(\";\").at(-1)`"},{"id":"/root/children/161/children/12","type":"text","loc":{"start":29758,"end":29798,"line":{"s":835,"e":835,"code":["| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |"]},"column":{"s":121,"e":161}},"dim":["","paragraph.161","text.12"],"code":" (leaf of the semicolon-trail, matching "},{"id":"/root/children/161/children/13","type":"inlineCode","loc":{"start":29798,"end":29815,"line":{"s":835,"e":835,"code":["| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |"]},"column":{"s":161,"e":178}},"dim":["","paragraph.161","inlineCode.13"],"code":"`cmdDashboard.js`"},{"id":"/root/children/161/children/14","type":"text","loc":{"start":29815,"end":29818,"line":{"s":835,"e":835,"code":["| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |"]},"column":{"s":178,"e":181}},"dim":["","paragraph.161","text.14"],"code":" / "},{"id":"/root/children/161/children/15","type":"inlineCode","loc":{"start":29818,"end":29834,"line":{"s":835,"e":835,"code":["| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |"]},"column":{"s":181,"e":197}},"dim":["","paragraph.161","inlineCode.15"],"code":"`cmdTreeview.js`"},{"id":"/root/children/161/children/16","type":"text","loc":{"start":29834,"end":29849,"line":{"s":835,"e":835,"code":["| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |"]},"column":{"s":197,"e":212}},"dim":["","paragraph.161","text.16"],"code":" convention) → "},{"id":"/root/children/161/children/17","type":"inlineCode","loc":{"start":29849,"end":29863,"line":{"s":835,"e":835,"code":["| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |"]},"column":{"s":212,"e":226}},"dim":["","paragraph.161","inlineCode.17"],"code":"`trail.at(-1)`"},{"id":"/root/children/161/children/18","type":"text","loc":{"start":29863,"end":29886,"line":{"s":835,"e":835,"code":["| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |"]},"column":{"s":226,"e":249}},"dim":["","paragraph.161","text.18"],"code":" (parsed-array form) → "},{"id":"/root/children/161/children/19","type":"inlineCode","loc":{"start":29886,"end":29892,"line":{"s":835,"e":835,"code":["| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |"]},"column":{"s":249,"e":255}},"dim":["","paragraph.161","inlineCode.19"],"code":"`name`"},{"id":"/root/children/161/children/20","type":"text","loc":{"start":29892,"end":29931,"line":{"s":835,"e":836,"code":["| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |","| `fn`                               | Source file path                                                                                                                                                                                                                                           |"]},"column":{"s":255,"e":2}},"dim":["","paragraph.161","text.20"],"code":" (URL-style, last-resort fallback) |\n| "},{"id":"/root/children/161/children/21","type":"inlineCode","loc":{"start":29931,"end":29935,"line":{"s":836,"e":836,"code":["| `fn`                               | Source file path                                                                                                                                                                                                                                           |"]},"column":{"s":2,"e":6}},"dim":["","paragraph.161","inlineCode.21"],"code":"`fn`"},{"id":"/root/children/161/children/22","type":"text","loc":{"start":29935,"end":30223,"line":{"s":836,"e":837,"code":["| `fn`                               | Source file path                                                                                                                                                                                                                                           |","| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |"]},"column":{"s":6,"e":2}},"dim":["","paragraph.161","text.22"],"code":"                               | Source file path                                                                                                                                                                                                                                           |\n| "},{"id":"/root/children/161/children/23","type":"inlineCode","loc":{"start":30223,"end":30230,"line":{"s":837,"e":837,"code":["| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |"]},"column":{"s":2,"e":9}},"dim":["","paragraph.161","inlineCode.23"],"code":"`trail`"},{"id":"/root/children/161/children/24","type":"text","loc":{"start":30230,"end":30515,"line":{"s":837,"e":838,"code":["| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |","| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |"]},"column":{"s":9,"e":2}},"dim":["","paragraph.161","text.24"],"code":" (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| "},{"id":"/root/children/161/children/25","type":"inlineCode","loc":{"start":30515,"end":30521,"line":{"s":838,"e":838,"code":["| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |"]},"column":{"s":2,"e":8}},"dim":["","paragraph.161","inlineCode.25"],"code":"`num1`"},{"id":"/root/children/161/children/26","type":"text","loc":{"start":30521,"end":30804,"line":{"s":838,"e":838,"code":["| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |"]},"column":{"s":8,"e":291}},"dim":["","paragraph.161","text.26"],"code":" (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |"},{"id":"/root/children/162","type":"paragraph","loc":{"start":30806,"end":31022,"line":{"s":840,"e":842,"code":["Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),","are skipped with `console.warn`. **If every item is skipped, a visible","blockquote is emitted** explaining why — the verb never fails silently."]},"column":{"s":0,"e":71}},"dim":["","paragraph.162"],"code":"Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently."},{"id":"/root/children/162/children/0","type":"text","loc":{"start":30806,"end":30820,"line":{"s":840,"e":840,"code":["Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),"]},"column":{"s":0,"e":14}},"dim":["","paragraph.162","text.0"],"code":"Items missing "},{"id":"/root/children/162/children/1","type":"inlineCode","loc":{"start":30820,"end":30826,"line":{"s":840,"e":840,"code":["Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),"]},"column":{"s":14,"e":20}},"dim":["","paragraph.162","inlineCode.1"],"code":"`name`"},{"id":"/root/children/162/children/2","type":"text","loc":{"start":30826,"end":30827,"line":{"s":840,"e":840,"code":["Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),"]},"column":{"s":20,"e":21}},"dim":["","paragraph.162","text.2"],"code":"/"},{"id":"/root/children/162/children/3","type":"inlineCode","loc":{"start":30827,"end":30832,"line":{"s":840,"e":840,"code":["Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),"]},"column":{"s":21,"e":26}},"dim":["","paragraph.162","inlineCode.3"],"code":"`ref`"},{"id":"/root/children/162/children/4","type":"text","loc":{"start":30832,"end":30850,"line":{"s":840,"e":840,"code":["Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),"]},"column":{"s":26,"e":44}},"dim":["","paragraph.162","text.4"],"code":", or without both "},{"id":"/root/children/162/children/5","type":"inlineCode","loc":{"start":30850,"end":30854,"line":{"s":840,"e":840,"code":["Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),"]},"column":{"s":44,"e":48}},"dim":["","paragraph.162","inlineCode.5"],"code":"`fn`"},{"id":"/root/children/162/children/6","type":"text","loc":{"start":30854,"end":30860,"line":{"s":840,"e":840,"code":["Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),"]},"column":{"s":48,"e":54}},"dim":["","paragraph.162","text.6"],"code":" and ("},{"id":"/root/children/162/children/7","type":"inlineCode","loc":{"start":30860,"end":30867,"line":{"s":840,"e":840,"code":["Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),"]},"column":{"s":54,"e":61}},"dim":["","paragraph.162","inlineCode.7"],"code":"`trail`"},{"id":"/root/children/162/children/8","type":"text","loc":{"start":30867,"end":30871,"line":{"s":840,"e":840,"code":["Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),"]},"column":{"s":61,"e":65}},"dim":["","paragraph.162","text.8"],"code":" or "},{"id":"/root/children/162/children/9","type":"inlineCode","loc":{"start":30871,"end":30877,"line":{"s":840,"e":840,"code":["Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),"]},"column":{"s":65,"e":71}},"dim":["","paragraph.162","inlineCode.9"],"code":"`num1`"},{"id":"/root/children/162/children/10","type":"text","loc":{"start":30877,"end":30897,"line":{"s":840,"e":841,"code":["Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),","are skipped with `console.warn`. **If every item is skipped, a visible"]},"column":{"s":71,"e":17}},"dim":["","paragraph.162","text.10"],"code":"),\nare skipped with "},{"id":"/root/children/162/children/11","type":"inlineCode","loc":{"start":30897,"end":30911,"line":{"s":841,"e":841,"code":["are skipped with `console.warn`. **If every item is skipped, a visible"]},"column":{"s":17,"e":31}},"dim":["","paragraph.162","inlineCode.11"],"code":"`console.warn`"},{"id":"/root/children/162/children/12","type":"text","loc":{"start":30911,"end":30913,"line":{"s":841,"e":841,"code":["are skipped with `console.warn`. **If every item is skipped, a visible"]},"column":{"s":31,"e":33}},"dim":["","paragraph.162","text.12"],"code":". "},{"id":"/root/children/162/children/13","type":"strong","loc":{"start":30913,"end":30974,"line":{"s":841,"e":842,"code":["are skipped with `console.warn`. **If every item is skipped, a visible","blockquote is emitted** explaining why — the verb never fails silently."]},"column":{"s":33,"e":23}},"dim":["","paragraph.162","strong.13"],"code":"**If every item is skipped, a visible\nblockquote is emitted**"},{"id":"/root/children/162/children/13/children/0","type":"text","loc":{"start":30915,"end":30972,"line":{"s":841,"e":842,"code":["are skipped with `console.warn`. **If every item is skipped, a visible","blockquote is emitted** explaining why — the verb never fails silently."]},"column":{"s":35,"e":21}},"dim":["","paragraph.162","strong.13","text.0"],"code":"If every item is skipped, a visible\nblockquote is emitted"},{"id":"/root/children/162/children/14","type":"text","loc":{"start":30974,"end":31022,"line":{"s":842,"e":842,"code":["blockquote is emitted** explaining why — the verb never fails silently."]},"column":{"s":23,"e":71}},"dim":["","paragraph.162","text.14"],"code":" explaining why — the verb never fails silently."},{"id":"/root/children/163","type":"paragraph","loc":{"start":31024,"end":31260,"line":{"s":844,"e":847,"code":["The common cause is feeding it the wrong search source: `files` results","(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no","subtree to resolve. Use a `fragments` query, whose items carry","`nomen`/`trail`/`num1`/`fn`."]},"column":{"s":0,"e":28}},"dim":["","paragraph.163"],"code":"The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`."},{"id":"/root/children/163/children/0","type":"text","loc":{"start":31024,"end":31080,"line":{"s":844,"e":844,"code":["The common cause is feeding it the wrong search source: `files` results"]},"column":{"s":0,"e":56}},"dim":["","paragraph.163","text.0"],"code":"The common cause is feeding it the wrong search source: "},{"id":"/root/children/163/children/1","type":"inlineCode","loc":{"start":31080,"end":31087,"line":{"s":844,"e":844,"code":["The common cause is feeding it the wrong search source: `files` results"]},"column":{"s":56,"e":63}},"dim":["","paragraph.163","inlineCode.1"],"code":"`files`"},{"id":"/root/children/163/children/2","type":"text","loc":{"start":31087,"end":31097,"line":{"s":844,"e":845,"code":["The common cause is feeding it the wrong search source: `files` results","(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no"]},"column":{"s":63,"e":1}},"dim":["","paragraph.163","text.2"],"code":" results\n("},{"id":"/root/children/163/children/3","type":"inlineCode","loc":{"start":31097,"end":31127,"line":{"s":845,"e":845,"code":["(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no"]},"column":{"s":1,"e":31}},"dim":["","paragraph.163","inlineCode.3"],"code":"`{name, uri, fn, type:\"file\"}`"},{"id":"/root/children/163/children/4","type":"text","loc":{"start":31127,"end":31138,"line":{"s":845,"e":845,"code":["(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no"]},"column":{"s":31,"e":42}},"dim":["","paragraph.163","text.4"],"code":") carry no "},{"id":"/root/children/163/children/5","type":"inlineCode","loc":{"start":31138,"end":31145,"line":{"s":845,"e":845,"code":["(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no"]},"column":{"s":42,"e":49}},"dim":["","paragraph.163","inlineCode.5"],"code":"`trail`"},{"id":"/root/children/163/children/6","type":"text","loc":{"start":31145,"end":31146,"line":{"s":845,"e":845,"code":["(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no"]},"column":{"s":49,"e":50}},"dim":["","paragraph.163","text.6"],"code":"/"},{"id":"/root/children/163/children/7","type":"inlineCode","loc":{"start":31146,"end":31152,"line":{"s":845,"e":845,"code":["(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no"]},"column":{"s":50,"e":56}},"dim":["","paragraph.163","inlineCode.7"],"code":"`num1`"},{"id":"/root/children/163/children/8","type":"text","loc":{"start":31152,"end":31195,"line":{"s":845,"e":846,"code":["(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no","subtree to resolve. Use a `fragments` query, whose items carry"]},"column":{"s":56,"e":26}},"dim":["","paragraph.163","text.8"],"code":", so there is no\nsubtree to resolve. Use a "},{"id":"/root/children/163/children/9","type":"inlineCode","loc":{"start":31195,"end":31206,"line":{"s":846,"e":846,"code":["subtree to resolve. Use a `fragments` query, whose items carry"]},"column":{"s":26,"e":37}},"dim":["","paragraph.163","inlineCode.9"],"code":"`fragments`"},{"id":"/root/children/163/children/10","type":"text","loc":{"start":31206,"end":31232,"line":{"s":846,"e":847,"code":["subtree to resolve. Use a `fragments` query, whose items carry","`nomen`/`trail`/`num1`/`fn`."]},"column":{"s":37,"e":0}},"dim":["","paragraph.163","text.10"],"code":" query, whose items carry\n"},{"id":"/root/children/163/children/11","type":"inlineCode","loc":{"start":31232,"end":31239,"line":{"s":847,"e":847,"code":["`nomen`/`trail`/`num1`/`fn`."]},"column":{"s":0,"e":7}},"dim":["","paragraph.163","inlineCode.11"],"code":"`nomen`"},{"id":"/root/children/163/children/12","type":"text","loc":{"start":31239,"end":31240,"line":{"s":847,"e":847,"code":["`nomen`/`trail`/`num1`/`fn`."]},"column":{"s":7,"e":8}},"dim":["","paragraph.163","text.12"],"code":"/"},{"id":"/root/children/163/children/13","type":"inlineCode","loc":{"start":31240,"end":31247,"line":{"s":847,"e":847,"code":["`nomen`/`trail`/`num1`/`fn`."]},"column":{"s":8,"e":15}},"dim":["","paragraph.163","inlineCode.13"],"code":"`trail`"},{"id":"/root/children/163/children/14","type":"text","loc":{"start":31247,"end":31248,"line":{"s":847,"e":847,"code":["`nomen`/`trail`/`num1`/`fn`."]},"column":{"s":15,"e":16}},"dim":["","paragraph.163","text.14"],"code":"/"},{"id":"/root/children/163/children/15","type":"inlineCode","loc":{"start":31248,"end":31254,"line":{"s":847,"e":847,"code":["`nomen`/`trail`/`num1`/`fn`."]},"column":{"s":16,"e":22}},"dim":["","paragraph.163","inlineCode.15"],"code":"`num1`"},{"id":"/root/children/163/children/16","type":"text","loc":{"start":31254,"end":31255,"line":{"s":847,"e":847,"code":["`nomen`/`trail`/`num1`/`fn`."]},"column":{"s":22,"e":23}},"dim":["","paragraph.163","text.16"],"code":"/"},{"id":"/root/children/163/children/17","type":"inlineCode","loc":{"start":31255,"end":31259,"line":{"s":847,"e":847,"code":["`nomen`/`trail`/`num1`/`fn`."]},"column":{"s":23,"e":27}},"dim":["","paragraph.163","inlineCode.17"],"code":"`fn`"},{"id":"/root/children/163/children/18","type":"text","loc":{"start":31259,"end":31260,"line":{"s":847,"e":847,"code":["`nomen`/`trail`/`num1`/`fn`."]},"column":{"s":27,"e":28}},"dim":["","paragraph.163","text.18"],"code":"."},{"id":"/root/children/164","type":"paragraph","loc":{"start":31262,"end":31271,"line":{"s":849,"e":849,"code":["**opts:**"]},"column":{"s":0,"e":9}},"dim":["","paragraph.164"],"code":"**opts:**"},{"id":"/root/children/164/children/0","type":"strong","loc":{"start":31262,"end":31271,"line":{"s":849,"e":849,"code":["**opts:**"]},"column":{"s":0,"e":9}},"dim":["","paragraph.164","strong.0"],"code":"**opts:**"},{"id":"/root/children/164/children/0/children/0","type":"text","loc":{"start":31264,"end":31269,"line":{"s":849,"e":849,"code":["**opts:**"]},"column":{"s":2,"e":7}},"dim":["","paragraph.164","strong.0","text.0"],"code":"opts:"},{"id":"/root/children/165","type":"paragraph","loc":{"start":31273,"end":31497,"line":{"s":851,"e":853,"code":["| Field   | Purpose                                                      |","| ------- | ------------------------------------------------------------ |","| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |"]},"column":{"s":0,"e":74}},"dim":["","paragraph.165"],"code":"| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |"},{"id":"/root/children/165/children/0","type":"text","loc":{"start":31273,"end":31425,"line":{"s":851,"e":853,"code":["| Field   | Purpose                                                      |","| ------- | ------------------------------------------------------------ |","| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.165","text.0"],"code":"| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| "},{"id":"/root/children/165/children/1","type":"inlineCode","loc":{"start":31425,"end":31432,"line":{"s":853,"e":853,"code":["| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |"]},"column":{"s":2,"e":9}},"dim":["","paragraph.165","inlineCode.1"],"code":"`depth`"},{"id":"/root/children/165/children/2","type":"text","loc":{"start":31432,"end":31472,"line":{"s":853,"e":853,"code":["| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |"]},"column":{"s":9,"e":49}},"dim":["","paragraph.165","text.2"],"code":" | Absolute override of the auto depth ("},{"id":"/root/children/165/children/3","type":"inlineCode","loc":{"start":31472,"end":31494,"line":{"s":853,"e":853,"code":["| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |"]},"column":{"s":49,"e":71}},"dim":["","paragraph.165","inlineCode.3"],"code":"`extruction.depth + 1`"},{"id":"/root/children/165/children/4","type":"text","loc":{"start":31494,"end":31497,"line":{"s":853,"e":853,"code":["| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |"]},"column":{"s":71,"e":74}},"dim":["","paragraph.165","text.4"],"code":") |"},{"id":"/root/children/166","type":"paragraph","loc":{"start":31499,"end":31633,"line":{"s":855,"e":856,"code":["**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If","`loadRefBody` is not provided, each Fragment renders heading-only."]},"column":{"s":0,"e":66}},"dim":["","paragraph.166"],"code":"**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only."},{"id":"/root/children/166/children/0","type":"strong","loc":{"start":31499,"end":31523,"line":{"s":855,"e":855,"code":["**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If"]},"column":{"s":0,"e":24}},"dim":["","paragraph.166","strong.0"],"code":"**Runner opt required:**"},{"id":"/root/children/166/children/0/children/0","type":"text","loc":{"start":31501,"end":31521,"line":{"s":855,"e":855,"code":["**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If"]},"column":{"s":2,"e":22}},"dim":["","paragraph.166","strong.0","text.0"],"code":"Runner opt required:"},{"id":"/root/children/166/children/1","type":"text","loc":{"start":31523,"end":31524,"line":{"s":855,"e":855,"code":["**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If"]},"column":{"s":24,"e":25}},"dim":["","paragraph.166","text.1"],"code":" "},{"id":"/root/children/166/children/2","type":"inlineCode","loc":{"start":31524,"end":31562,"line":{"s":855,"e":855,"code":["**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If"]},"column":{"s":25,"e":63}},"dim":["","paragraph.166","inlineCode.2"],"code":"`runner(ctx, { evalFn, loadRefBody })`"},{"id":"/root/children/166/children/3","type":"text","loc":{"start":31562,"end":31567,"line":{"s":855,"e":856,"code":["**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If","`loadRefBody` is not provided, each Fragment renders heading-only."]},"column":{"s":63,"e":0}},"dim":["","paragraph.166","text.3"],"code":". If\n"},{"id":"/root/children/166/children/4","type":"inlineCode","loc":{"start":31567,"end":31580,"line":{"s":856,"e":856,"code":["`loadRefBody` is not provided, each Fragment renders heading-only."]},"column":{"s":0,"e":13}},"dim":["","paragraph.166","inlineCode.4"],"code":"`loadRefBody`"},{"id":"/root/children/166/children/5","type":"text","loc":{"start":31580,"end":31633,"line":{"s":856,"e":856,"code":["`loadRefBody` is not provided, each Fragment renders heading-only."]},"column":{"s":13,"e":66}},"dim":["","paragraph.166","text.5"],"code":" is not provided, each Fragment renders heading-only."},{"id":"/root/children/167","type":"heading","loc":{"start":31635,"end":31673,"line":{"s":858,"e":858,"code":["#### `insertNljson(collection, opts?)`"]},"column":{"s":0,"e":38}},"dim":["","heading.167"],"code":"#### `insertNljson(collection, opts?)`","symbName":"heading","symbRange":[31675,32320],"symbRangeL":[858,884],"outerCode":"\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.","outerHtml":"\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>"},{"id":"/root/children/167/children/0","type":"inlineCode","loc":{"start":31640,"end":31673,"line":{"s":858,"e":858,"code":["#### `insertNljson(collection, opts?)`"]},"column":{"s":5,"e":38}},"dim":["","heading.167","inlineCode.0"],"code":"`insertNljson(collection, opts?)`"},{"id":"/root/children/168","type":"paragraph","loc":{"start":31675,"end":31781,"line":{"s":860,"e":861,"code":["Serialize a collection as newline-delimited JSON inside an ` ```nljson `","fence — one JSON object per line:"]},"column":{"s":0,"e":33}},"dim":["","paragraph.168"],"code":"Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:"},{"id":"/root/children/168/children/0","type":"text","loc":{"start":31675,"end":31734,"line":{"s":860,"e":860,"code":["Serialize a collection as newline-delimited JSON inside an ` ```nljson `"]},"column":{"s":0,"e":59}},"dim":["","paragraph.168","text.0"],"code":"Serialize a collection as newline-delimited JSON inside an "},{"id":"/root/children/168/children/1","type":"inlineCode","loc":{"start":31734,"end":31747,"line":{"s":860,"e":860,"code":["Serialize a collection as newline-delimited JSON inside an ` ```nljson `"]},"column":{"s":59,"e":72}},"dim":["","paragraph.168","inlineCode.1"],"code":"` ```nljson `"},{"id":"/root/children/168/children/2","type":"text","loc":{"start":31747,"end":31781,"line":{"s":860,"e":861,"code":["Serialize a collection as newline-delimited JSON inside an ` ```nljson `","fence — one JSON object per line:"]},"column":{"s":72,"e":33}},"dim":["","paragraph.168","text.2"],"code":"\nfence — one JSON object per line:"},{"id":"/root/children/169","type":"code","loc":{"start":31784,"end":31871,"line":{"s":864,"e":870,"code":["```","## ${rows}","","\\`\\`\\`javascript","return [insertNljson([{ a: 1 }, { b: 2 }])]","\\`\\`\\`","```"]},"column":{"s":0,"e":3}},"dim":["","code.169"],"code":"```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```","symbName":"code","symbRange":[31874,58640],"symbRangeL":[null,872],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n"},{"id":"/root/children/170","type":"code","loc":{"start":31874,"end":31903,"line":{"s":873,"e":876,"code":["```nljson","{\"a\":1}","{\"b\":2}","```"]},"column":{"s":0,"e":3}},"dim":["","code.170"],"code":"```nljson\n{\"a\":1}\n{\"b\":2}\n```","symbName":"code","symbRange":[31905,32476],"symbRangeL":[null,890],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n"},{"id":"/root/children/171","type":"paragraph","loc":{"start":31905,"end":32320,"line":{"s":878,"e":883,"code":["A single non-array value is wrapped. This is a **raw passthrough** — values","are serialized as given, so nested objects and arrays survive. That makes it","unsuitable for feeding a table directly: `insertNljson(await search(...))`","emits `trail` arrays, and Tabulator's `html` formatter throws","`Formatter has returned a type of object`. Use `insertRefsAsNljson` for","table-bound ref data, or pick scalar fields yourself."]},"column":{"s":0,"e":53}},"dim":["","paragraph.171"],"code":"A single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself."},{"id":"/root/children/171/children/0","type":"text","loc":{"start":31905,"end":31952,"line":{"s":878,"e":878,"code":["A single non-array value is wrapped. This is a **raw passthrough** — values"]},"column":{"s":0,"e":47}},"dim":["","paragraph.171","text.0"],"code":"A single non-array value is wrapped. This is a "},{"id":"/root/children/171/children/1","type":"strong","loc":{"start":31952,"end":31971,"line":{"s":878,"e":878,"code":["A single non-array value is wrapped. This is a **raw passthrough** — values"]},"column":{"s":47,"e":66}},"dim":["","paragraph.171","strong.1"],"code":"**raw passthrough**"},{"id":"/root/children/171/children/1/children/0","type":"text","loc":{"start":31954,"end":31969,"line":{"s":878,"e":878,"code":["A single non-array value is wrapped. This is a **raw passthrough** — values"]},"column":{"s":49,"e":64}},"dim":["","paragraph.171","strong.1","text.0"],"code":"raw passthrough"},{"id":"/root/children/171/children/2","type":"text","loc":{"start":31971,"end":32099,"line":{"s":878,"e":880,"code":["A single non-array value is wrapped. This is a **raw passthrough** — values","are serialized as given, so nested objects and arrays survive. That makes it","unsuitable for feeding a table directly: `insertNljson(await search(...))`"]},"column":{"s":66,"e":41}},"dim":["","paragraph.171","text.2"],"code":" — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: "},{"id":"/root/children/171/children/3","type":"inlineCode","loc":{"start":32099,"end":32132,"line":{"s":880,"e":880,"code":["unsuitable for feeding a table directly: `insertNljson(await search(...))`"]},"column":{"s":41,"e":74}},"dim":["","paragraph.171","inlineCode.3"],"code":"`insertNljson(await search(...))`"},{"id":"/root/children/171/children/4","type":"text","loc":{"start":32132,"end":32139,"line":{"s":880,"e":881,"code":["unsuitable for feeding a table directly: `insertNljson(await search(...))`","emits `trail` arrays, and Tabulator's `html` formatter throws"]},"column":{"s":74,"e":6}},"dim":["","paragraph.171","text.4"],"code":"\nemits "},{"id":"/root/children/171/children/5","type":"inlineCode","loc":{"start":32139,"end":32146,"line":{"s":881,"e":881,"code":["emits `trail` arrays, and Tabulator's `html` formatter throws"]},"column":{"s":6,"e":13}},"dim":["","paragraph.171","inlineCode.5"],"code":"`trail`"},{"id":"/root/children/171/children/6","type":"text","loc":{"start":32146,"end":32171,"line":{"s":881,"e":881,"code":["emits `trail` arrays, and Tabulator's `html` formatter throws"]},"column":{"s":13,"e":38}},"dim":["","paragraph.171","text.6"],"code":" arrays, and Tabulator's "},{"id":"/root/children/171/children/7","type":"inlineCode","loc":{"start":32171,"end":32177,"line":{"s":881,"e":881,"code":["emits `trail` arrays, and Tabulator's `html` formatter throws"]},"column":{"s":38,"e":44}},"dim":["","paragraph.171","inlineCode.7"],"code":"`html`"},{"id":"/root/children/171/children/8","type":"text","loc":{"start":32177,"end":32195,"line":{"s":881,"e":882,"code":["emits `trail` arrays, and Tabulator's `html` formatter throws","`Formatter has returned a type of object`. Use `insertRefsAsNljson` for"]},"column":{"s":44,"e":0}},"dim":["","paragraph.171","text.8"],"code":" formatter throws\n"},{"id":"/root/children/171/children/9","type":"inlineCode","loc":{"start":32195,"end":32236,"line":{"s":882,"e":882,"code":["`Formatter has returned a type of object`. Use `insertRefsAsNljson` for"]},"column":{"s":0,"e":41}},"dim":["","paragraph.171","inlineCode.9"],"code":"`Formatter has returned a type of object`"},{"id":"/root/children/171/children/10","type":"text","loc":{"start":32236,"end":32242,"line":{"s":882,"e":882,"code":["`Formatter has returned a type of object`. Use `insertRefsAsNljson` for"]},"column":{"s":41,"e":47}},"dim":["","paragraph.171","text.10"],"code":". Use "},{"id":"/root/children/171/children/11","type":"inlineCode","loc":{"start":32242,"end":32262,"line":{"s":882,"e":882,"code":["`Formatter has returned a type of object`. Use `insertRefsAsNljson` for"]},"column":{"s":47,"e":67}},"dim":["","paragraph.171","inlineCode.11"],"code":"`insertRefsAsNljson`"},{"id":"/root/children/171/children/12","type":"text","loc":{"start":32262,"end":32320,"line":{"s":882,"e":883,"code":["`Formatter has returned a type of object`. Use `insertRefsAsNljson` for","table-bound ref data, or pick scalar fields yourself."]},"column":{"s":67,"e":53}},"dim":["","paragraph.171","text.12"],"code":" for\ntable-bound ref data, or pick scalar fields yourself."},{"id":"/root/children/172","type":"heading","loc":{"start":32322,"end":32359,"line":{"s":885,"e":885,"code":["#### `insertRefsAsList(items, opts?)`"]},"column":{"s":0,"e":37}},"dim":["","heading.172"],"code":"#### `insertRefsAsList(items, opts?)`","symbName":"heading","symbRange":[32361,33165],"symbRangeL":[885,915],"outerCode":"\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |","outerHtml":"\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>"},{"id":"/root/children/172/children/0","type":"inlineCode","loc":{"start":32327,"end":32359,"line":{"s":885,"e":885,"code":["#### `insertRefsAsList(items, opts?)`"]},"column":{"s":5,"e":37}},"dim":["","heading.172","inlineCode.0"],"code":"`insertRefsAsList(items, opts?)`"},{"id":"/root/children/173","type":"paragraph","loc":{"start":32361,"end":32476,"line":{"s":887,"e":888,"code":["Render an array of refs as a markdown bullet list — a flat alternative to","`insertRefsAsSubtree` with no lazy fetch:"]},"column":{"s":0,"e":41}},"dim":["","paragraph.173"],"code":"Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:"},{"id":"/root/children/173/children/0","type":"text","loc":{"start":32361,"end":32435,"line":{"s":887,"e":888,"code":["Render an array of refs as a markdown bullet list — a flat alternative to","`insertRefsAsSubtree` with no lazy fetch:"]},"column":{"s":0,"e":0}},"dim":["","paragraph.173","text.0"],"code":"Render an array of refs as a markdown bullet list — a flat alternative to\n"},{"id":"/root/children/173/children/1","type":"inlineCode","loc":{"start":32435,"end":32456,"line":{"s":888,"e":888,"code":["`insertRefsAsSubtree` with no lazy fetch:"]},"column":{"s":0,"e":21}},"dim":["","paragraph.173","inlineCode.1"],"code":"`insertRefsAsSubtree`"},{"id":"/root/children/173/children/2","type":"text","loc":{"start":32456,"end":32476,"line":{"s":888,"e":888,"code":["`insertRefsAsSubtree` with no lazy fetch:"]},"column":{"s":21,"e":41}},"dim":["","paragraph.173","text.2"],"code":" with no lazy fetch:"},{"id":"/root/children/174","type":"code","loc":{"start":32479,"end":32575,"line":{"s":891,"e":897,"code":["```","## ${links}","","\\`\\`\\`javascript","return [insertRefsAsList(await search(_mdt_label))]","\\`\\`\\`","```"]},"column":{"s":0,"e":3}},"dim":["","code.174"],"code":"```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```","symbName":"code","symbRange":[32578,58640],"symbRangeL":[null,899],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n"},{"id":"/root/children/175","type":"code","loc":{"start":32578,"end":32675,"line":{"s":900,"e":904,"code":["```","- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}","- [login](#/paper/a.mdd)","- plain","```"]},"column":{"s":0,"e":3}},"dim":["","code.175"],"code":"```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```","symbName":"code","symbRange":[32677,33360],"symbRangeL":[null,921],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n"},{"id":"/root/children/176","type":"paragraph","loc":{"start":32677,"end":32884,"line":{"s":906,"e":908,"code":["Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item","with `uri` becomes a markdown link; without one it stays plain text. Items","with no resolvable label are skipped with `console.warn`."]},"column":{"s":0,"e":57}},"dim":["","paragraph.176"],"code":"Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`."},{"id":"/root/children/176/children/0","type":"text","loc":{"start":32677,"end":32721,"line":{"s":906,"e":906,"code":["Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item"]},"column":{"s":0,"e":44}},"dim":["","paragraph.176","text.0"],"code":"Labels resolve with the same 4-step rule as "},{"id":"/root/children/176/children/1","type":"inlineCode","loc":{"start":32721,"end":32742,"line":{"s":906,"e":906,"code":["Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item"]},"column":{"s":44,"e":65}},"dim":["","paragraph.176","inlineCode.1"],"code":"`insertRefsAsSubtree`"},{"id":"/root/children/176/children/2","type":"text","loc":{"start":32742,"end":32757,"line":{"s":906,"e":907,"code":["Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item","with `uri` becomes a markdown link; without one it stays plain text. Items"]},"column":{"s":65,"e":5}},"dim":["","paragraph.176","text.2"],"code":". An item\nwith "},{"id":"/root/children/176/children/3","type":"inlineCode","loc":{"start":32757,"end":32762,"line":{"s":907,"e":907,"code":["with `uri` becomes a markdown link; without one it stays plain text. Items"]},"column":{"s":5,"e":10}},"dim":["","paragraph.176","inlineCode.3"],"code":"`uri`"},{"id":"/root/children/176/children/4","type":"text","loc":{"start":32762,"end":32869,"line":{"s":907,"e":908,"code":["with `uri` becomes a markdown link; without one it stays plain text. Items","with no resolvable label are skipped with `console.warn`."]},"column":{"s":10,"e":42}},"dim":["","paragraph.176","text.4"],"code":" becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with "},{"id":"/root/children/176/children/5","type":"inlineCode","loc":{"start":32869,"end":32883,"line":{"s":908,"e":908,"code":["with no resolvable label are skipped with `console.warn`."]},"column":{"s":42,"e":56}},"dim":["","paragraph.176","inlineCode.5"],"code":"`console.warn`"},{"id":"/root/children/176/children/6","type":"text","loc":{"start":32883,"end":32884,"line":{"s":908,"e":908,"code":["with no resolvable label are skipped with `console.warn`."]},"column":{"s":56,"e":57}},"dim":["","paragraph.176","text.6"],"code":"."},{"id":"/root/children/177","type":"paragraph","loc":{"start":32886,"end":33165,"line":{"s":910,"e":914,"code":["| opts     | Purpose                                  |","| -------- | ---------------------------------------- |","| `bullet` | List marker, default `\"-\"`               |","| `data`   | `false` suppresses the `{…}` data suffix |","| `source` | Conversion-tree provenance tag           |"]},"column":{"s":0,"e":55}},"dim":["","paragraph.177"],"code":"| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |"},{"id":"/root/children/177/children/0","type":"text","loc":{"start":32886,"end":33000,"line":{"s":910,"e":912,"code":["| opts     | Purpose                                  |","| -------- | ---------------------------------------- |","| `bullet` | List marker, default `\"-\"`               |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.177","text.0"],"code":"| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| "},{"id":"/root/children/177/children/1","type":"inlineCode","loc":{"start":33000,"end":33008,"line":{"s":912,"e":912,"code":["| `bullet` | List marker, default `\"-\"`               |"]},"column":{"s":2,"e":10}},"dim":["","paragraph.177","inlineCode.1"],"code":"`bullet`"},{"id":"/root/children/177/children/2","type":"text","loc":{"start":33008,"end":33032,"line":{"s":912,"e":912,"code":["| `bullet` | List marker, default `\"-\"`               |"]},"column":{"s":10,"e":34}},"dim":["","paragraph.177","text.2"],"code":" | List marker, default "},{"id":"/root/children/177/children/3","type":"inlineCode","loc":{"start":33032,"end":33037,"line":{"s":912,"e":912,"code":["| `bullet` | List marker, default `\"-\"`               |"]},"column":{"s":34,"e":39}},"dim":["","paragraph.177","inlineCode.3"],"code":"`\"-\"`"},{"id":"/root/children/177/children/4","type":"text","loc":{"start":33037,"end":33056,"line":{"s":912,"e":913,"code":["| `bullet` | List marker, default `\"-\"`               |","| `data`   | `false` suppresses the `{…}` data suffix |"]},"column":{"s":39,"e":2}},"dim":["","paragraph.177","text.4"],"code":"               |\n| "},{"id":"/root/children/177/children/5","type":"inlineCode","loc":{"start":33056,"end":33062,"line":{"s":913,"e":913,"code":["| `data`   | `false` suppresses the `{…}` data suffix |"]},"column":{"s":2,"e":8}},"dim":["","paragraph.177","inlineCode.5"],"code":"`data`"},{"id":"/root/children/177/children/6","type":"text","loc":{"start":33062,"end":33067,"line":{"s":913,"e":913,"code":["| `data`   | `false` suppresses the `{…}` data suffix |"]},"column":{"s":8,"e":13}},"dim":["","paragraph.177","text.6"],"code":"   | "},{"id":"/root/children/177/children/7","type":"inlineCode","loc":{"start":33067,"end":33074,"line":{"s":913,"e":913,"code":["| `data`   | `false` suppresses the `{…}` data suffix |"]},"column":{"s":13,"e":20}},"dim":["","paragraph.177","inlineCode.7"],"code":"`false`"},{"id":"/root/children/177/children/8","type":"text","loc":{"start":33074,"end":33090,"line":{"s":913,"e":913,"code":["| `data`   | `false` suppresses the `{…}` data suffix |"]},"column":{"s":20,"e":36}},"dim":["","paragraph.177","text.8"],"code":" suppresses the "},{"id":"/root/children/177/children/9","type":"inlineCode","loc":{"start":33090,"end":33095,"line":{"s":913,"e":913,"code":["| `data`   | `false` suppresses the `{…}` data suffix |"]},"column":{"s":36,"e":41}},"dim":["","paragraph.177","inlineCode.9"],"code":"`{…}`"},{"id":"/root/children/177/children/10","type":"text","loc":{"start":33095,"end":33112,"line":{"s":913,"e":914,"code":["| `data`   | `false` suppresses the `{…}` data suffix |","| `source` | Conversion-tree provenance tag           |"]},"column":{"s":41,"e":2}},"dim":["","paragraph.177","text.10"],"code":" data suffix |\n| "},{"id":"/root/children/177/children/11","type":"inlineCode","loc":{"start":33112,"end":33120,"line":{"s":914,"e":914,"code":["| `source` | Conversion-tree provenance tag           |"]},"column":{"s":2,"e":10}},"dim":["","paragraph.177","inlineCode.11"],"code":"`source`"},{"id":"/root/children/177/children/12","type":"text","loc":{"start":33120,"end":33165,"line":{"s":914,"e":914,"code":["| `source` | Conversion-tree provenance tag           |"]},"column":{"s":10,"e":55}},"dim":["","paragraph.177","text.12"],"code":" | Conversion-tree provenance tag           |"},{"id":"/root/children/178","type":"heading","loc":{"start":33167,"end":33210,"line":{"s":916,"e":916,"code":["#### `insertRefsAsNljson(items, optsOrFn?)`"]},"column":{"s":0,"e":43}},"dim":["","heading.178"],"code":"#### `insertRefsAsNljson(items, optsOrFn?)`","symbName":"heading","symbRange":[33212,35535],"symbRangeL":[916,974],"outerCode":"\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |","outerHtml":"\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>"},{"id":"/root/children/178/children/0","type":"inlineCode","loc":{"start":33172,"end":33210,"line":{"s":916,"e":916,"code":["#### `insertRefsAsNljson(items, optsOrFn?)`"]},"column":{"s":5,"e":43}},"dim":["","heading.178","inlineCode.0"],"code":"`insertRefsAsNljson(items, optsOrFn?)`"},{"id":"/root/children/179","type":"paragraph","loc":{"start":33212,"end":33360,"line":{"s":918,"e":919,"code":["Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but","builds each row from the ref and guarantees **table-safe scalar cells**:"]},"column":{"s":0,"e":72}},"dim":["","paragraph.179"],"code":"Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:"},{"id":"/root/children/179/children/0","type":"text","loc":{"start":33212,"end":33260,"line":{"s":918,"e":918,"code":["Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but"]},"column":{"s":0,"e":48}},"dim":["","paragraph.179","text.0"],"code":"Render an array of refs as nljson rows — reuses "},{"id":"/root/children/179/children/1","type":"inlineCode","loc":{"start":33260,"end":33274,"line":{"s":918,"e":918,"code":["Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but"]},"column":{"s":48,"e":62}},"dim":["","paragraph.179","inlineCode.1"],"code":"`insertNljson`"},{"id":"/root/children/179/children/2","type":"text","loc":{"start":33274,"end":33332,"line":{"s":918,"e":919,"code":["Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but","builds each row from the ref and guarantees **table-safe scalar cells**:"]},"column":{"s":62,"e":44}},"dim":["","paragraph.179","text.2"],"code":"'s fence, but\nbuilds each row from the ref and guarantees "},{"id":"/root/children/179/children/3","type":"strong","loc":{"start":33332,"end":33359,"line":{"s":919,"e":919,"code":["builds each row from the ref and guarantees **table-safe scalar cells**:"]},"column":{"s":44,"e":71}},"dim":["","paragraph.179","strong.3"],"code":"**table-safe scalar cells**"},{"id":"/root/children/179/children/3/children/0","type":"text","loc":{"start":33334,"end":33357,"line":{"s":919,"e":919,"code":["builds each row from the ref and guarantees **table-safe scalar cells**:"]},"column":{"s":46,"e":69}},"dim":["","paragraph.179","strong.3","text.0"],"code":"table-safe scalar cells"},{"id":"/root/children/179/children/4","type":"text","loc":{"start":33359,"end":33360,"line":{"s":919,"e":919,"code":["builds each row from the ref and guarantees **table-safe scalar cells**:"]},"column":{"s":71,"e":72}},"dim":["","paragraph.179","text.4"],"code":":"},{"id":"/root/children/180","type":"code","loc":{"start":33363,"end":33461,"line":{"s":922,"e":928,"code":["```","## ${table}","","\\`\\`\\`javascript","return [insertRefsAsNljson(await search(_mdt_label))]","\\`\\`\\`","```"]},"column":{"s":0,"e":3}},"dim":["","code.180"],"code":"```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```","symbName":"code","symbRange":[33464,58640],"symbRangeL":[null,930],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n"},{"id":"/root/children/181","type":"code","loc":{"start":33464,"end":33567,"line":{"s":931,"e":933,"code":["```nljson","{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}","```"]},"column":{"s":0,"e":3}},"dim":["","code.181"],"code":"```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```","symbName":"code","symbRange":[33569,34072],"symbRangeL":[null,946],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n"},{"id":"/root/children/182","type":"paragraph","loc":{"start":33569,"end":33792,"line":{"s":935,"e":937,"code":["`link` is an **HTML anchor** (not a markdown link) because nljson usually","feeds a table — the table needs `columnDefaults: { formatter: 'html' }` to","render it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`)."]},"column":{"s":0,"e":74}},"dim":["","paragraph.182"],"code":"`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`)."},{"id":"/root/children/182/children/0","type":"inlineCode","loc":{"start":33569,"end":33575,"line":{"s":935,"e":935,"code":["`link` is an **HTML anchor** (not a markdown link) because nljson usually"]},"column":{"s":0,"e":6}},"dim":["","paragraph.182","inlineCode.0"],"code":"`link`"},{"id":"/root/children/182/children/1","type":"text","loc":{"start":33575,"end":33582,"line":{"s":935,"e":935,"code":["`link` is an **HTML anchor** (not a markdown link) because nljson usually"]},"column":{"s":6,"e":13}},"dim":["","paragraph.182","text.1"],"code":" is an "},{"id":"/root/children/182/children/2","type":"strong","loc":{"start":33582,"end":33597,"line":{"s":935,"e":935,"code":["`link` is an **HTML anchor** (not a markdown link) because nljson usually"]},"column":{"s":13,"e":28}},"dim":["","paragraph.182","strong.2"],"code":"**HTML anchor**"},{"id":"/root/children/182/children/2/children/0","type":"text","loc":{"start":33584,"end":33595,"line":{"s":935,"e":935,"code":["`link` is an **HTML anchor** (not a markdown link) because nljson usually"]},"column":{"s":15,"e":26}},"dim":["","paragraph.182","strong.2","text.0"],"code":"HTML anchor"},{"id":"/root/children/182/children/3","type":"text","loc":{"start":33597,"end":33675,"line":{"s":935,"e":936,"code":["`link` is an **HTML anchor** (not a markdown link) because nljson usually","feeds a table — the table needs `columnDefaults: { formatter: 'html' }` to"]},"column":{"s":28,"e":32}},"dim":["","paragraph.182","text.3"],"code":" (not a markdown link) because nljson usually\nfeeds a table — the table needs "},{"id":"/root/children/182/children/4","type":"inlineCode","loc":{"start":33675,"end":33714,"line":{"s":936,"e":936,"code":["feeds a table — the table needs `columnDefaults: { formatter: 'html' }` to"]},"column":{"s":32,"e":71}},"dim":["","paragraph.182","inlineCode.4"],"code":"`columnDefaults: { formatter: 'html' }`"},{"id":"/root/children/182/children/5","type":"text","loc":{"start":33714,"end":33733,"line":{"s":936,"e":937,"code":["feeds a table — the table needs `columnDefaults: { formatter: 'html' }` to","render it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`)."]},"column":{"s":71,"e":15}},"dim":["","paragraph.182","text.5"],"code":" to\nrender it. The "},{"id":"/root/children/182/children/6","type":"inlineCode","loc":{"start":33733,"end":33738,"line":{"s":937,"e":937,"code":["render it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`)."]},"column":{"s":15,"e":20}},"dim":["","paragraph.182","inlineCode.6"],"code":"`uri`"},{"id":"/root/children/182/children/7","type":"text","loc":{"start":33738,"end":33761,"line":{"s":937,"e":937,"code":["render it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`)."]},"column":{"s":20,"e":43}},"dim":["","paragraph.182","text.7"],"code":" is attribute-escaped ("},{"id":"/root/children/182/children/8","type":"inlineCode","loc":{"start":33761,"end":33764,"line":{"s":937,"e":937,"code":["render it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`)."]},"column":{"s":43,"e":46}},"dim":["","paragraph.182","inlineCode.8"],"code":"`&`"},{"id":"/root/children/182/children/9","type":"text","loc":{"start":33764,"end":33767,"line":{"s":937,"e":937,"code":["render it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`)."]},"column":{"s":46,"e":49}},"dim":["","paragraph.182","text.9"],"code":" → "},{"id":"/root/children/182/children/10","type":"inlineCode","loc":{"start":33767,"end":33774,"line":{"s":937,"e":937,"code":["render it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`)."]},"column":{"s":49,"e":56}},"dim":["","paragraph.182","inlineCode.10"],"code":"`&amp;`"},{"id":"/root/children/182/children/11","type":"text","loc":{"start":33774,"end":33776,"line":{"s":937,"e":937,"code":["render it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`)."]},"column":{"s":56,"e":58}},"dim":["","paragraph.182","text.11"],"code":", "},{"id":"/root/children/182/children/12","type":"inlineCode","loc":{"start":33776,"end":33779,"line":{"s":937,"e":937,"code":["render it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`)."]},"column":{"s":58,"e":61}},"dim":["","paragraph.182","inlineCode.12"],"code":"`\"`"},{"id":"/root/children/182/children/13","type":"text","loc":{"start":33779,"end":33782,"line":{"s":937,"e":937,"code":["render it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`)."]},"column":{"s":61,"e":64}},"dim":["","paragraph.182","text.13"],"code":" → "},{"id":"/root/children/182/children/14","type":"inlineCode","loc":{"start":33782,"end":33790,"line":{"s":937,"e":937,"code":["render it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`)."]},"column":{"s":64,"e":72}},"dim":["","paragraph.182","inlineCode.14"],"code":"`&quot;`"},{"id":"/root/children/182/children/15","type":"text","loc":{"start":33790,"end":33792,"line":{"s":937,"e":937,"code":["render it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`)."]},"column":{"s":72,"e":74}},"dim":["","paragraph.182","text.15"],"code":")."},{"id":"/root/children/183","type":"paragraph","loc":{"start":33794,"end":33978,"line":{"s":939,"e":941,"code":["Every row value is flattened before output: any object or array becomes a","JSON string. This is what keeps Tabulator's `html` formatter from throwing","on `trail` arrays or nested `data`."]},"column":{"s":0,"e":35}},"dim":["","paragraph.183"],"code":"Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`."},{"id":"/root/children/183/children/0","type":"text","loc":{"start":33794,"end":33912,"line":{"s":939,"e":940,"code":["Every row value is flattened before output: any object or array becomes a","JSON string. This is what keeps Tabulator's `html` formatter from throwing"]},"column":{"s":0,"e":44}},"dim":["","paragraph.183","text.0"],"code":"Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's "},{"id":"/root/children/183/children/1","type":"inlineCode","loc":{"start":33912,"end":33918,"line":{"s":940,"e":940,"code":["JSON string. This is what keeps Tabulator's `html` formatter from throwing"]},"column":{"s":44,"e":50}},"dim":["","paragraph.183","inlineCode.1"],"code":"`html`"},{"id":"/root/children/183/children/2","type":"text","loc":{"start":33918,"end":33946,"line":{"s":940,"e":941,"code":["JSON string. This is what keeps Tabulator's `html` formatter from throwing","on `trail` arrays or nested `data`."]},"column":{"s":50,"e":3}},"dim":["","paragraph.183","text.2"],"code":" formatter from throwing\non "},{"id":"/root/children/183/children/3","type":"inlineCode","loc":{"start":33946,"end":33953,"line":{"s":941,"e":941,"code":["on `trail` arrays or nested `data`."]},"column":{"s":3,"e":10}},"dim":["","paragraph.183","inlineCode.3"],"code":"`trail`"},{"id":"/root/children/183/children/4","type":"text","loc":{"start":33953,"end":33971,"line":{"s":941,"e":941,"code":["on `trail` arrays or nested `data`."]},"column":{"s":10,"e":28}},"dim":["","paragraph.183","text.4"],"code":" arrays or nested "},{"id":"/root/children/183/children/5","type":"inlineCode","loc":{"start":33971,"end":33977,"line":{"s":941,"e":941,"code":["on `trail` arrays or nested `data`."]},"column":{"s":28,"e":34}},"dim":["","paragraph.183","inlineCode.5"],"code":"`data`"},{"id":"/root/children/183/children/6","type":"text","loc":{"start":33977,"end":33978,"line":{"s":941,"e":941,"code":["on `trail` arrays or nested `data`."]},"column":{"s":34,"e":35}},"dim":["","paragraph.183","text.6"],"code":"."},{"id":"/root/children/184","type":"paragraph","loc":{"start":33980,"end":34072,"line":{"s":943,"e":944,"code":["**Second argument — object or function.** A bare function is shorthand for","`{ extend: fn }`:"]},"column":{"s":0,"e":17}},"dim":["","paragraph.184"],"code":"**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:"},{"id":"/root/children/184/children/0","type":"strong","loc":{"start":33980,"end":34021,"line":{"s":943,"e":943,"code":["**Second argument — object or function.** A bare function is shorthand for"]},"column":{"s":0,"e":41}},"dim":["","paragraph.184","strong.0"],"code":"**Second argument — object or function.**"},{"id":"/root/children/184/children/0/children/0","type":"text","loc":{"start":33982,"end":34019,"line":{"s":943,"e":943,"code":["**Second argument — object or function.** A bare function is shorthand for"]},"column":{"s":2,"e":39}},"dim":["","paragraph.184","strong.0","text.0"],"code":"Second argument — object or function."},{"id":"/root/children/184/children/1","type":"text","loc":{"start":34021,"end":34055,"line":{"s":943,"e":944,"code":["**Second argument — object or function.** A bare function is shorthand for","`{ extend: fn }`:"]},"column":{"s":41,"e":0}},"dim":["","paragraph.184","text.1"],"code":" A bare function is shorthand for\n"},{"id":"/root/children/184/children/2","type":"inlineCode","loc":{"start":34055,"end":34071,"line":{"s":944,"e":944,"code":["`{ extend: fn }`:"]},"column":{"s":0,"e":16}},"dim":["","paragraph.184","inlineCode.2"],"code":"`{ extend: fn }`"},{"id":"/root/children/184/children/3","type":"text","loc":{"start":34071,"end":34072,"line":{"s":944,"e":944,"code":["`{ extend: fn }`:"]},"column":{"s":16,"e":17}},"dim":["","paragraph.184","text.3"],"code":":"},{"id":"/root/children/185","type":"code","loc":{"start":34075,"end":34340,"line":{"s":947,"e":959,"code":["```","\\`\\`\\`javascript","return [","  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {","    const data = i.data ? JSON.parse(i.data) : undefined","    return {","      suma: data?.platba?.suma,","      data: JSON.stringify(data),","    }","  }),","]","\\`\\`\\`","```"]},"column":{"s":0,"e":3}},"dim":["","code.185"],"code":"```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```","symbName":"code","symbRange":[34342,35672],"symbRangeL":[null,980],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n"},{"id":"/root/children/186","type":"paragraph","loc":{"start":34342,"end":34673,"line":{"s":961,"e":965,"code":["`extend(item, row)` receives the **raw** item first (so `item.data` is the","untouched string) plus the base row, and its returned props are merged over","the auto-built ones — the example above replaces the auto `data`. Keys whose","value is `undefined` are dropped from the row rather than emitted as `null`,","so ragged rows are normal."]},"column":{"s":0,"e":26}},"dim":["","paragraph.186"],"code":"`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal."},{"id":"/root/children/186/children/0","type":"inlineCode","loc":{"start":34342,"end":34361,"line":{"s":961,"e":961,"code":["`extend(item, row)` receives the **raw** item first (so `item.data` is the"]},"column":{"s":0,"e":19}},"dim":["","paragraph.186","inlineCode.0"],"code":"`extend(item, row)`"},{"id":"/root/children/186/children/1","type":"text","loc":{"start":34361,"end":34375,"line":{"s":961,"e":961,"code":["`extend(item, row)` receives the **raw** item first (so `item.data` is the"]},"column":{"s":19,"e":33}},"dim":["","paragraph.186","text.1"],"code":" receives the "},{"id":"/root/children/186/children/2","type":"strong","loc":{"start":34375,"end":34382,"line":{"s":961,"e":961,"code":["`extend(item, row)` receives the **raw** item first (so `item.data` is the"]},"column":{"s":33,"e":40}},"dim":["","paragraph.186","strong.2"],"code":"**raw**"},{"id":"/root/children/186/children/2/children/0","type":"text","loc":{"start":34377,"end":34380,"line":{"s":961,"e":961,"code":["`extend(item, row)` receives the **raw** item first (so `item.data` is the"]},"column":{"s":35,"e":38}},"dim":["","paragraph.186","strong.2","text.0"],"code":"raw"},{"id":"/root/children/186/children/3","type":"text","loc":{"start":34382,"end":34398,"line":{"s":961,"e":961,"code":["`extend(item, row)` receives the **raw** item first (so `item.data` is the"]},"column":{"s":40,"e":56}},"dim":["","paragraph.186","text.3"],"code":" item first (so "},{"id":"/root/children/186/children/4","type":"inlineCode","loc":{"start":34398,"end":34409,"line":{"s":961,"e":961,"code":["`extend(item, row)` receives the **raw** item first (so `item.data` is the"]},"column":{"s":56,"e":67}},"dim":["","paragraph.186","inlineCode.4"],"code":"`item.data`"},{"id":"/root/children/186/children/5","type":"text","loc":{"start":34409,"end":34551,"line":{"s":961,"e":963,"code":["`extend(item, row)` receives the **raw** item first (so `item.data` is the","untouched string) plus the base row, and its returned props are merged over","the auto-built ones — the example above replaces the auto `data`. Keys whose"]},"column":{"s":67,"e":58}},"dim":["","paragraph.186","text.5"],"code":" is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto "},{"id":"/root/children/186/children/6","type":"inlineCode","loc":{"start":34551,"end":34557,"line":{"s":963,"e":963,"code":["the auto-built ones — the example above replaces the auto `data`. Keys whose"]},"column":{"s":58,"e":64}},"dim":["","paragraph.186","inlineCode.6"],"code":"`data`"},{"id":"/root/children/186/children/7","type":"text","loc":{"start":34557,"end":34579,"line":{"s":963,"e":964,"code":["the auto-built ones — the example above replaces the auto `data`. Keys whose","value is `undefined` are dropped from the row rather than emitted as `null`,"]},"column":{"s":64,"e":9}},"dim":["","paragraph.186","text.7"],"code":". Keys whose\nvalue is "},{"id":"/root/children/186/children/8","type":"inlineCode","loc":{"start":34579,"end":34590,"line":{"s":964,"e":964,"code":["value is `undefined` are dropped from the row rather than emitted as `null`,"]},"column":{"s":9,"e":20}},"dim":["","paragraph.186","inlineCode.8"],"code":"`undefined`"},{"id":"/root/children/186/children/9","type":"text","loc":{"start":34590,"end":34639,"line":{"s":964,"e":964,"code":["value is `undefined` are dropped from the row rather than emitted as `null`,"]},"column":{"s":20,"e":69}},"dim":["","paragraph.186","text.9"],"code":" are dropped from the row rather than emitted as "},{"id":"/root/children/186/children/10","type":"inlineCode","loc":{"start":34639,"end":34645,"line":{"s":964,"e":964,"code":["value is `undefined` are dropped from the row rather than emitted as `null`,"]},"column":{"s":69,"e":75}},"dim":["","paragraph.186","inlineCode.10"],"code":"`null`"},{"id":"/root/children/186/children/11","type":"text","loc":{"start":34645,"end":34673,"line":{"s":964,"e":965,"code":["value is `undefined` are dropped from the row rather than emitted as `null`,","so ragged rows are normal."]},"column":{"s":75,"e":26}},"dim":["","paragraph.186","text.11"],"code":",\nso ragged rows are normal."},{"id":"/root/children/187","type":"paragraph","loc":{"start":34675,"end":35535,"line":{"s":967,"e":973,"code":["| opts     | Purpose                                                                                                     |","| -------- | ----------------------------------------------------------------------------------------------------------- |","| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |","| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |","| `data`   | `false` drops the auto `data` column                                                                        |","| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |","| `source` | Conversion-tree provenance tag                                                                              |"]},"column":{"s":0,"e":122}},"dim":["","paragraph.187"],"code":"| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |"},{"id":"/root/children/187/children/0","type":"text","loc":{"start":34675,"end":34923,"line":{"s":967,"e":969,"code":["| opts     | Purpose                                                                                                     |","| -------- | ----------------------------------------------------------------------------------------------------------- |","| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.187","text.0"],"code":"| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| "},{"id":"/root/children/187/children/1","type":"inlineCode","loc":{"start":34923,"end":34931,"line":{"s":969,"e":969,"code":["| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |"]},"column":{"s":2,"e":10}},"dim":["","paragraph.187","inlineCode.1"],"code":"`extend`"},{"id":"/root/children/187/children/2","type":"text","loc":{"start":34931,"end":34934,"line":{"s":969,"e":969,"code":["| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |"]},"column":{"s":10,"e":13}},"dim":["","paragraph.187","text.2"],"code":" | "},{"id":"/root/children/187/children/3","type":"inlineCode","loc":{"start":34934,"end":34956,"line":{"s":969,"e":969,"code":["| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |"]},"column":{"s":13,"e":35}},"dim":["","paragraph.187","inlineCode.3"],"code":"`(item, row) => ({…})`"},{"id":"/root/children/187/children/4","type":"text","loc":{"start":34956,"end":35046,"line":{"s":969,"e":970,"code":["| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |","| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |"]},"column":{"s":35,"e":2}},"dim":["","paragraph.187","text.4"],"code":" — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| "},{"id":"/root/children/187/children/5","type":"inlineCode","loc":{"start":35046,"end":35054,"line":{"s":970,"e":970,"code":["| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |"]},"column":{"s":2,"e":10}},"dim":["","paragraph.187","inlineCode.5"],"code":"`fields`"},{"id":"/root/children/187/children/6","type":"text","loc":{"start":35054,"end":35105,"line":{"s":970,"e":970,"code":["| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |"]},"column":{"s":10,"e":61}},"dim":["","paragraph.187","text.6"],"code":" | Array of item field names to copy through, e.g. "},{"id":"/root/children/187/children/7","type":"inlineCode","loc":{"start":35105,"end":35119,"line":{"s":970,"e":970,"code":["| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |"]},"column":{"s":61,"e":75}},"dim":["","paragraph.187","inlineCode.7"],"code":"`['scaledTs']`"},{"id":"/root/children/187/children/8","type":"text","loc":{"start":35119,"end":35169,"line":{"s":970,"e":971,"code":["| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |","| `data`   | `false` drops the auto `data` column                                                                        |"]},"column":{"s":75,"e":2}},"dim":["","paragraph.187","text.8"],"code":"                                              |\n| "},{"id":"/root/children/187/children/9","type":"inlineCode","loc":{"start":35169,"end":35175,"line":{"s":971,"e":971,"code":["| `data`   | `false` drops the auto `data` column                                                                        |"]},"column":{"s":2,"e":8}},"dim":["","paragraph.187","inlineCode.9"],"code":"`data`"},{"id":"/root/children/187/children/10","type":"text","loc":{"start":35175,"end":35180,"line":{"s":971,"e":971,"code":["| `data`   | `false` drops the auto `data` column                                                                        |"]},"column":{"s":8,"e":13}},"dim":["","paragraph.187","text.10"],"code":"   | "},{"id":"/root/children/187/children/11","type":"inlineCode","loc":{"start":35180,"end":35187,"line":{"s":971,"e":971,"code":["| `data`   | `false` drops the auto `data` column                                                                        |"]},"column":{"s":13,"e":20}},"dim":["","paragraph.187","inlineCode.11"],"code":"`false`"},{"id":"/root/children/187/children/12","type":"text","loc":{"start":35187,"end":35203,"line":{"s":971,"e":971,"code":["| `data`   | `false` drops the auto `data` column                                                                        |"]},"column":{"s":20,"e":36}},"dim":["","paragraph.187","text.12"],"code":" drops the auto "},{"id":"/root/children/187/children/13","type":"inlineCode","loc":{"start":35203,"end":35209,"line":{"s":971,"e":971,"code":["| `data`   | `false` drops the auto `data` column                                                                        |"]},"column":{"s":36,"e":42}},"dim":["","paragraph.187","inlineCode.13"],"code":"`data`"},{"id":"/root/children/187/children/14","type":"text","loc":{"start":35209,"end":35292,"line":{"s":971,"e":972,"code":["| `data`   | `false` drops the auto `data` column                                                                        |","| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |"]},"column":{"s":42,"e":2}},"dim":["","paragraph.187","text.14"],"code":" column                                                                        |\n| "},{"id":"/root/children/187/children/15","type":"inlineCode","loc":{"start":35292,"end":35297,"line":{"s":972,"e":972,"code":["| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |"]},"column":{"s":2,"e":7}},"dim":["","paragraph.187","inlineCode.15"],"code":"`map`"},{"id":"/root/children/187/children/16","type":"text","loc":{"start":35297,"end":35303,"line":{"s":972,"e":972,"code":["| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |"]},"column":{"s":7,"e":13}},"dim":["","paragraph.187","text.16"],"code":"    | "},{"id":"/root/children/187/children/17","type":"inlineCode","loc":{"start":35303,"end":35323,"line":{"s":972,"e":972,"code":["| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |"]},"column":{"s":13,"e":33}},"dim":["","paragraph.187","inlineCode.17"],"code":"`(row, item) => row`"},{"id":"/root/children/187/children/18","type":"text","loc":{"start":35323,"end":35361,"line":{"s":972,"e":972,"code":["| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |"]},"column":{"s":33,"e":71}},"dim":["","paragraph.187","text.18"],"code":" — replaces the whole row; runs after "},{"id":"/root/children/187/children/19","type":"inlineCode","loc":{"start":35361,"end":35369,"line":{"s":972,"e":972,"code":["| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |"]},"column":{"s":71,"e":79}},"dim":["","paragraph.187","inlineCode.19"],"code":"`extend`"},{"id":"/root/children/187/children/20","type":"text","loc":{"start":35369,"end":35415,"line":{"s":972,"e":973,"code":["| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |","| `source` | Conversion-tree provenance tag                                                                              |"]},"column":{"s":79,"e":2}},"dim":["","paragraph.187","text.20"],"code":" and sees parsed values before flattening |\n| "},{"id":"/root/children/187/children/21","type":"inlineCode","loc":{"start":35415,"end":35423,"line":{"s":973,"e":973,"code":["| `source` | Conversion-tree provenance tag                                                                              |"]},"column":{"s":2,"e":10}},"dim":["","paragraph.187","inlineCode.21"],"code":"`source`"},{"id":"/root/children/187/children/22","type":"text","loc":{"start":35423,"end":35535,"line":{"s":973,"e":973,"code":["| `source` | Conversion-tree provenance tag                                                                              |"]},"column":{"s":10,"e":122}},"dim":["","paragraph.187","text.22"],"code":" | Conversion-tree provenance tag                                                                              |"},{"id":"/root/children/188","type":"heading","loc":{"start":35537,"end":35572,"line":{"s":975,"e":975,"code":["#### `buildUrl(content, mimeType?)`"]},"column":{"s":0,"e":35}},"dim":["","heading.188"],"code":"#### `buildUrl(content, mimeType?)`","symbName":"heading","symbRange":[35574,35791],"symbRangeL":[975,986],"outerCode":"\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```","outerHtml":"\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>"},{"id":"/root/children/188/children/0","type":"inlineCode","loc":{"start":35542,"end":35572,"line":{"s":975,"e":975,"code":["#### `buildUrl(content, mimeType?)`"]},"column":{"s":5,"e":35}},"dim":["","heading.188","inlineCode.0"],"code":"`buildUrl(content, mimeType?)`"},{"id":"/root/children/189","type":"paragraph","loc":{"start":35574,"end":35672,"line":{"s":977,"e":978,"code":["Not a command — a plain helper returning a base64 data URI via `btoa()`.","Defaults to `text/plain`:"]},"column":{"s":0,"e":25}},"dim":["","paragraph.189"],"code":"Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:"},{"id":"/root/children/189/children/0","type":"text","loc":{"start":35574,"end":35637,"line":{"s":977,"e":977,"code":["Not a command — a plain helper returning a base64 data URI via `btoa()`."]},"column":{"s":0,"e":63}},"dim":["","paragraph.189","text.0"],"code":"Not a command — a plain helper returning a base64 data URI via "},{"id":"/root/children/189/children/1","type":"inlineCode","loc":{"start":35637,"end":35645,"line":{"s":977,"e":977,"code":["Not a command — a plain helper returning a base64 data URI via `btoa()`."]},"column":{"s":63,"e":71}},"dim":["","paragraph.189","inlineCode.1"],"code":"`btoa()`"},{"id":"/root/children/189/children/2","type":"text","loc":{"start":35645,"end":35659,"line":{"s":977,"e":978,"code":["Not a command — a plain helper returning a base64 data URI via `btoa()`.","Defaults to `text/plain`:"]},"column":{"s":71,"e":12}},"dim":["","paragraph.189","text.2"],"code":".\nDefaults to "},{"id":"/root/children/189/children/3","type":"inlineCode","loc":{"start":35659,"end":35671,"line":{"s":978,"e":978,"code":["Defaults to `text/plain`:"]},"column":{"s":12,"e":24}},"dim":["","paragraph.189","inlineCode.3"],"code":"`text/plain`"},{"id":"/root/children/189/children/4","type":"text","loc":{"start":35671,"end":35672,"line":{"s":978,"e":978,"code":["Defaults to `text/plain`:"]},"column":{"s":24,"e":25}},"dim":["","paragraph.189","text.4"],"code":":"},{"id":"/root/children/190","type":"code","loc":{"start":35675,"end":35791,"line":{"s":981,"e":985,"code":["```","\\`\\`\\`javascript","return [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]","\\`\\`\\`","```"]},"column":{"s":0,"e":3}},"dim":["","code.190"],"code":"```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```","symbName":"code","symbRange":[35793,35875],"symbRangeL":[null,991],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n"},{"id":"/root/children/191","type":"heading","loc":{"start":35793,"end":35810,"line":{"s":987,"e":987,"code":["#### Mixed output"]},"column":{"s":0,"e":17}},"dim":["","heading.191"],"code":"#### Mixed output","symbName":"heading","symbRange":[35812,36282],"symbRangeL":[987,1005],"outerCode":"\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.","outerHtml":"\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>"},{"id":"/root/children/191/children/0","type":"text","loc":{"start":35798,"end":35810,"line":{"s":987,"e":987,"code":["#### Mixed output"]},"column":{"s":5,"e":17}},"dim":["","heading.191","text.0"],"code":"Mixed output"},{"id":"/root/children/192","type":"paragraph","loc":{"start":35812,"end":35875,"line":{"s":989,"e":989,"code":["Return an array of calls to produce multiple items in sequence:"]},"column":{"s":0,"e":63}},"dim":["","paragraph.192"],"code":"Return an array of calls to produce multiple items in sequence:"},{"id":"/root/children/192/children/0","type":"text","loc":{"start":35812,"end":35875,"line":{"s":989,"e":989,"code":["Return an array of calls to produce multiple items in sequence:"]},"column":{"s":0,"e":63}},"dim":["","paragraph.192","text.0"],"code":"Return an array of calls to produce multiple items in sequence:"},{"id":"/root/children/193","type":"code","loc":{"start":35878,"end":36068,"line":{"s":992,"e":1000,"code":["```","## ${mixed}","","\\`\\`\\`javascript","const items = await search(\"mdd\")","const cards = items.map(r => ({ /* fragment shape */ }))","return [inject(\"> Preview below:\\n\\n\"), insert(cards)]","\\`\\`\\`","```"]},"column":{"s":0,"e":3}},"dim":["","code.193"],"code":"```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```","symbName":"code","symbRange":[36070,36695],"symbRangeL":[null,1018],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n"},{"id":"/root/children/194","type":"paragraph","loc":{"start":36070,"end":36282,"line":{"s":1002,"e":1004,"code":["Each item in the array is a command object produced by any of the verbs —","`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,","`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order."]},"column":{"s":0,"e":74}},"dim":["","paragraph.194"],"code":"Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order."},{"id":"/root/children/194/children/0","type":"text","loc":{"start":36070,"end":36144,"line":{"s":1002,"e":1003,"code":["Each item in the array is a command object produced by any of the verbs —","`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,"]},"column":{"s":0,"e":0}},"dim":["","paragraph.194","text.0"],"code":"Each item in the array is a command object produced by any of the verbs —\n"},{"id":"/root/children/194/children/1","type":"inlineCode","loc":{"start":36144,"end":36154,"line":{"s":1003,"e":1003,"code":["`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,"]},"column":{"s":0,"e":10}},"dim":["","paragraph.194","inlineCode.1"],"code":"`insert()`"},{"id":"/root/children/194/children/2","type":"text","loc":{"start":36154,"end":36156,"line":{"s":1003,"e":1003,"code":["`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,"]},"column":{"s":10,"e":12}},"dim":["","paragraph.194","text.2"],"code":", "},{"id":"/root/children/194/children/3","type":"inlineCode","loc":{"start":36156,"end":36166,"line":{"s":1003,"e":1003,"code":["`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,"]},"column":{"s":12,"e":22}},"dim":["","paragraph.194","inlineCode.3"],"code":"`inject()`"},{"id":"/root/children/194/children/4","type":"text","loc":{"start":36166,"end":36168,"line":{"s":1003,"e":1003,"code":["`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,"]},"column":{"s":22,"e":24}},"dim":["","paragraph.194","text.4"],"code":", "},{"id":"/root/children/194/children/5","type":"inlineCode","loc":{"start":36168,"end":36184,"line":{"s":1003,"e":1003,"code":["`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,"]},"column":{"s":24,"e":40}},"dim":["","paragraph.194","inlineCode.5"],"code":"`insertNljson()`"},{"id":"/root/children/194/children/6","type":"text","loc":{"start":36184,"end":36186,"line":{"s":1003,"e":1003,"code":["`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,"]},"column":{"s":40,"e":42}},"dim":["","paragraph.194","text.6"],"code":", "},{"id":"/root/children/194/children/7","type":"inlineCode","loc":{"start":36186,"end":36206,"line":{"s":1003,"e":1003,"code":["`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,"]},"column":{"s":42,"e":62}},"dim":["","paragraph.194","inlineCode.7"],"code":"`insertRefsAsList()`"},{"id":"/root/children/194/children/8","type":"text","loc":{"start":36206,"end":36208,"line":{"s":1003,"e":1004,"code":["`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,","`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order."]},"column":{"s":62,"e":0}},"dim":["","paragraph.194","text.8"],"code":",\n"},{"id":"/root/children/194/children/9","type":"inlineCode","loc":{"start":36208,"end":36230,"line":{"s":1004,"e":1004,"code":["`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order."]},"column":{"s":0,"e":22}},"dim":["","paragraph.194","inlineCode.9"],"code":"`insertRefsAsNljson()`"},{"id":"/root/children/194/children/10","type":"text","loc":{"start":36230,"end":36235,"line":{"s":1004,"e":1004,"code":["`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order."]},"column":{"s":22,"e":27}},"dim":["","paragraph.194","text.10"],"code":", or "},{"id":"/root/children/194/children/11","type":"inlineCode","loc":{"start":36235,"end":36258,"line":{"s":1004,"e":1004,"code":["`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order."]},"column":{"s":27,"e":50}},"dim":["","paragraph.194","inlineCode.11"],"code":"`insertRefsAsSubtree()`"},{"id":"/root/children/194/children/12","type":"text","loc":{"start":36258,"end":36282,"line":{"s":1004,"e":1004,"code":["`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order."]},"column":{"s":50,"e":74}},"dim":["","paragraph.194","text.12"],"code":" — mixable in any order."},{"id":"/root/children/195","type":"heading","loc":{"start":36284,"end":36303,"line":{"s":1006,"e":1006,"code":["#### Return nothing"]},"column":{"s":0,"e":19}},"dim":["","heading.195"],"code":"#### Return nothing","symbName":"heading","symbRange":[36305,36569],"symbRangeL":[1006,1012],"outerCode":"\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).","outerHtml":"\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>"},{"id":"/root/children/195/children/0","type":"text","loc":{"start":36289,"end":36303,"line":{"s":1006,"e":1006,"code":["#### Return nothing"]},"column":{"s":5,"e":19}},"dim":["","heading.195","text.0"],"code":"Return nothing"},{"id":"/root/children/196","type":"list","loc":{"start":36305,"end":36569,"line":{"s":1008,"e":1011,"code":["- **Omit `return` or return `undefined`** — the extruction stays transparent","  (no output, children promoted as if the extruction didn't exist).","- **Return `null`** — the extruction is removed and its children are","  **suppressed** (dropped entirely, not promoted)."]},"column":{"s":0,"e":50}},"dim":["","list.196"],"code":"- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).","symbName":"list","symbRange":[36571,42899],"symbRangeL":[1008,1167],"outerCode":"  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**","outerHtml":"<p>  (no output, children promoted as if the extruction didn't exist).</p><ul><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>"},{"id":"/root/children/196/children/0","type":"listItem","loc":{"start":36305,"end":36449,"line":{"s":1008,"e":1009,"code":["- **Omit `return` or return `undefined`** — the extruction stays transparent","  (no output, children promoted as if the extruction didn't exist)."]},"column":{"s":0,"e":67}},"dim":["","list.196","listItem.0"],"code":"- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist)."},{"id":"/root/children/196/children/0/children/0","type":"paragraph","loc":{"start":36307,"end":36449,"line":{"s":1008,"e":1009,"code":["- **Omit `return` or return `undefined`** — the extruction stays transparent","  (no output, children promoted as if the extruction didn't exist)."]},"column":{"s":2,"e":67}},"dim":["","list.196","listItem.0","paragraph.0"],"code":"**Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist)."},{"id":"/root/children/196/children/0/children/0/children/0","type":"strong","loc":{"start":36307,"end":36346,"line":{"s":1008,"e":1008,"code":["- **Omit `return` or return `undefined`** — the extruction stays transparent"]},"column":{"s":2,"e":41}},"dim":["","list.196","listItem.0","paragraph.0","strong.0"],"code":"**Omit `return` or return `undefined`**"},{"id":"/root/children/196/children/0/children/0/children/0/children/0","type":"text","loc":{"start":36309,"end":36314,"line":{"s":1008,"e":1008,"code":["- **Omit `return` or return `undefined`** — the extruction stays transparent"]},"column":{"s":4,"e":9}},"dim":["","list.196","listItem.0","paragraph.0","strong.0","text.0"],"code":"Omit "},{"id":"/root/children/196/children/0/children/0/children/0/children/1","type":"inlineCode","loc":{"start":36314,"end":36322,"line":{"s":1008,"e":1008,"code":["- **Omit `return` or return `undefined`** — the extruction stays transparent"]},"column":{"s":9,"e":17}},"dim":["","list.196","listItem.0","paragraph.0","strong.0","inlineCode.1"],"code":"`return`"},{"id":"/root/children/196/children/0/children/0/children/0/children/2","type":"text","loc":{"start":36322,"end":36333,"line":{"s":1008,"e":1008,"code":["- **Omit `return` or return `undefined`** — the extruction stays transparent"]},"column":{"s":17,"e":28}},"dim":["","list.196","listItem.0","paragraph.0","strong.0","text.2"],"code":" or return "},{"id":"/root/children/196/children/0/children/0/children/0/children/3","type":"inlineCode","loc":{"start":36333,"end":36344,"line":{"s":1008,"e":1008,"code":["- **Omit `return` or return `undefined`** — the extruction stays transparent"]},"column":{"s":28,"e":39}},"dim":["","list.196","listItem.0","paragraph.0","strong.0","inlineCode.3"],"code":"`undefined`"},{"id":"/root/children/196/children/0/children/0/children/1","type":"text","loc":{"start":36346,"end":36449,"line":{"s":1008,"e":1009,"code":["- **Omit `return` or return `undefined`** — the extruction stays transparent","  (no output, children promoted as if the extruction didn't exist)."]},"column":{"s":41,"e":67}},"dim":["","list.196","listItem.0","paragraph.0","text.1"],"code":" — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist)."},{"id":"/root/children/196/children/1","type":"listItem","loc":{"start":36450,"end":36569,"line":{"s":1010,"e":1011,"code":["- **Return `null`** — the extruction is removed and its children are","  **suppressed** (dropped entirely, not promoted)."]},"column":{"s":0,"e":50}},"dim":["","list.196","listItem.1"],"code":"- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted)."},{"id":"/root/children/196/children/1/children/0","type":"paragraph","loc":{"start":36452,"end":36569,"line":{"s":1010,"e":1011,"code":["- **Return `null`** — the extruction is removed and its children are","  **suppressed** (dropped entirely, not promoted)."]},"column":{"s":2,"e":50}},"dim":["","list.196","listItem.1","paragraph.0"],"code":"**Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted)."},{"id":"/root/children/196/children/1/children/0/children/0","type":"strong","loc":{"start":36452,"end":36469,"line":{"s":1010,"e":1010,"code":["- **Return `null`** — the extruction is removed and its children are"]},"column":{"s":2,"e":19}},"dim":["","list.196","listItem.1","paragraph.0","strong.0"],"code":"**Return `null`**"},{"id":"/root/children/196/children/1/children/0/children/0/children/0","type":"text","loc":{"start":36454,"end":36461,"line":{"s":1010,"e":1010,"code":["- **Return `null`** — the extruction is removed and its children are"]},"column":{"s":4,"e":11}},"dim":["","list.196","listItem.1","paragraph.0","strong.0","text.0"],"code":"Return "},{"id":"/root/children/196/children/1/children/0/children/0/children/1","type":"inlineCode","loc":{"start":36461,"end":36467,"line":{"s":1010,"e":1010,"code":["- **Return `null`** — the extruction is removed and its children are"]},"column":{"s":11,"e":17}},"dim":["","list.196","listItem.1","paragraph.0","strong.0","inlineCode.1"],"code":"`null`"},{"id":"/root/children/196/children/1/children/0/children/1","type":"text","loc":{"start":36469,"end":36519,"line":{"s":1010,"e":1011,"code":["- **Return `null`** — the extruction is removed and its children are","  **suppressed** (dropped entirely, not promoted)."]},"column":{"s":19,"e":0}},"dim":["","list.196","listItem.1","paragraph.0","text.1"],"code":" — the extruction is removed and its children are\n"},{"id":"/root/children/196/children/1/children/0/children/2","type":"strong","loc":{"start":36521,"end":36535,"line":{"s":1011,"e":1011,"code":["  **suppressed** (dropped entirely, not promoted)."]},"column":{"s":2,"e":16}},"dim":["","list.196","listItem.1","paragraph.0","strong.2"],"code":"**suppressed**"},{"id":"/root/children/196/children/1/children/0/children/2/children/0","type":"text","loc":{"start":36523,"end":36533,"line":{"s":1011,"e":1011,"code":["  **suppressed** (dropped entirely, not promoted)."]},"column":{"s":4,"e":14}},"dim":["","list.196","listItem.1","paragraph.0","strong.2","text.0"],"code":"suppressed"},{"id":"/root/children/196/children/1/children/0/children/3","type":"text","loc":{"start":36535,"end":36569,"line":{"s":1011,"e":1011,"code":["  **suppressed** (dropped entirely, not promoted)."]},"column":{"s":16,"e":50}},"dim":["","list.196","listItem.1","paragraph.0","text.3"],"code":" (dropped entirely, not promoted)."},{"id":"/root/children/197","type":"heading","loc":{"start":36571,"end":36602,"line":{"s":1013,"e":1013,"code":["#### State still via `mdtState`"]},"column":{"s":0,"e":31}},"dim":["","heading.197"],"code":"#### State still via `mdtState`","symbName":"heading","symbRange":[36604,36859],"symbRangeL":[1013,1033],"outerCode":"\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```","outerHtml":"\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>"},{"id":"/root/children/197/children/0","type":"text","loc":{"start":36576,"end":36592,"line":{"s":1013,"e":1013,"code":["#### State still via `mdtState`"]},"column":{"s":5,"e":21}},"dim":["","heading.197","text.0"],"code":"State still via "},{"id":"/root/children/197/children/1","type":"inlineCode","loc":{"start":36592,"end":36602,"line":{"s":1013,"e":1013,"code":["#### State still via `mdtState`"]},"column":{"s":21,"e":31}},"dim":["","heading.197","inlineCode.1"],"code":"`mdtState`"},{"id":"/root/children/198","type":"paragraph","loc":{"start":36604,"end":36695,"line":{"s":1015,"e":1016,"code":["The `mdtState` object is mutated directly through property assignment, not","through helpers:"]},"column":{"s":0,"e":16}},"dim":["","paragraph.198"],"code":"The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:"},{"id":"/root/children/198/children/0","type":"text","loc":{"start":36604,"end":36608,"line":{"s":1015,"e":1015,"code":["The `mdtState` object is mutated directly through property assignment, not"]},"column":{"s":0,"e":4}},"dim":["","paragraph.198","text.0"],"code":"The "},{"id":"/root/children/198/children/1","type":"inlineCode","loc":{"start":36608,"end":36618,"line":{"s":1015,"e":1015,"code":["The `mdtState` object is mutated directly through property assignment, not"]},"column":{"s":4,"e":14}},"dim":["","paragraph.198","inlineCode.1"],"code":"`mdtState`"},{"id":"/root/children/198/children/2","type":"text","loc":{"start":36618,"end":36695,"line":{"s":1015,"e":1016,"code":["The `mdtState` object is mutated directly through property assignment, not","through helpers:"]},"column":{"s":14,"e":16}},"dim":["","paragraph.198","text.2"],"code":" object is mutated directly through property assignment, not\nthrough helpers:"},{"id":"/root/children/199","type":"code","loc":{"start":36698,"end":36859,"line":{"s":1019,"e":1032,"code":["```","## ${init}","","\\`\\`\\`javascript","mdtState.counter = 0","\\`\\`\\`","","## ${count}","","\\`\\`\\`javascript","mdtState.counter++","return inject(String(mdtState.counter))","\\`\\`\\`","```"]},"column":{"s":0,"e":3}},"dim":["","code.199"],"code":"```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```","symbName":"code","symbRange":[36861,38437],"symbRangeL":[null,1063],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n"},{"id":"/root/children/200","type":"heading","loc":{"start":36861,"end":36915,"line":{"s":1034,"e":1034,"code":["#### Adapters — `search`, `searchVotes`, `votesAsRefs`"]},"column":{"s":0,"e":54}},"dim":["","heading.200"],"code":"#### Adapters — `search`, `searchVotes`, `votesAsRefs`","symbName":"heading","symbRange":[36917,39958],"symbRangeL":[1034,1112],"outerCode":"\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.","outerHtml":"\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>"},{"id":"/root/children/200/children/0","type":"text","loc":{"start":36866,"end":36877,"line":{"s":1034,"e":1034,"code":["#### Adapters — `search`, `searchVotes`, `votesAsRefs`"]},"column":{"s":5,"e":16}},"dim":["","heading.200","text.0"],"code":"Adapters — "},{"id":"/root/children/200/children/1","type":"inlineCode","loc":{"start":36877,"end":36885,"line":{"s":1034,"e":1034,"code":["#### Adapters — `search`, `searchVotes`, `votesAsRefs`"]},"column":{"s":16,"e":24}},"dim":["","heading.200","inlineCode.1"],"code":"`search`"},{"id":"/root/children/200/children/2","type":"text","loc":{"start":36885,"end":36887,"line":{"s":1034,"e":1034,"code":["#### Adapters — `search`, `searchVotes`, `votesAsRefs`"]},"column":{"s":24,"e":26}},"dim":["","heading.200","text.2"],"code":", "},{"id":"/root/children/200/children/3","type":"inlineCode","loc":{"start":36887,"end":36900,"line":{"s":1034,"e":1034,"code":["#### Adapters — `search`, `searchVotes`, `votesAsRefs`"]},"column":{"s":26,"e":39}},"dim":["","heading.200","inlineCode.3"],"code":"`searchVotes`"},{"id":"/root/children/200/children/4","type":"text","loc":{"start":36900,"end":36902,"line":{"s":1034,"e":1034,"code":["#### Adapters — `search`, `searchVotes`, `votesAsRefs`"]},"column":{"s":39,"e":41}},"dim":["","heading.200","text.4"],"code":", "},{"id":"/root/children/200/children/5","type":"inlineCode","loc":{"start":36902,"end":36915,"line":{"s":1034,"e":1034,"code":["#### Adapters — `search`, `searchVotes`, `votesAsRefs`"]},"column":{"s":41,"e":54}},"dim":["","heading.200","inlineCode.5"],"code":"`votesAsRefs`"},{"id":"/root/children/201","type":"paragraph","loc":{"start":36917,"end":37134,"line":{"s":1036,"e":1038,"code":["Adapters are **not** commands. They are async functions injected into the","eval context by `createAdapters()` (`adapters.js`) and used to _obtain_","items, which the `insert*` verbs then render. All three are `await`-ed."]},"column":{"s":0,"e":71}},"dim":["","paragraph.201"],"code":"Adapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed."},{"id":"/root/children/201/children/0","type":"text","loc":{"start":36917,"end":36930,"line":{"s":1036,"e":1036,"code":["Adapters are **not** commands. They are async functions injected into the"]},"column":{"s":0,"e":13}},"dim":["","paragraph.201","text.0"],"code":"Adapters are "},{"id":"/root/children/201/children/1","type":"strong","loc":{"start":36930,"end":36937,"line":{"s":1036,"e":1036,"code":["Adapters are **not** commands. They are async functions injected into the"]},"column":{"s":13,"e":20}},"dim":["","paragraph.201","strong.1"],"code":"**not**"},{"id":"/root/children/201/children/1/children/0","type":"text","loc":{"start":36932,"end":36935,"line":{"s":1036,"e":1036,"code":["Adapters are **not** commands. They are async functions injected into the"]},"column":{"s":15,"e":18}},"dim":["","paragraph.201","strong.1","text.0"],"code":"not"},{"id":"/root/children/201/children/2","type":"text","loc":{"start":36937,"end":37007,"line":{"s":1036,"e":1037,"code":["Adapters are **not** commands. They are async functions injected into the","eval context by `createAdapters()` (`adapters.js`) and used to _obtain_"]},"column":{"s":20,"e":16}},"dim":["","paragraph.201","text.2"],"code":" commands. They are async functions injected into the\neval context by "},{"id":"/root/children/201/children/3","type":"inlineCode","loc":{"start":37007,"end":37025,"line":{"s":1037,"e":1037,"code":["eval context by `createAdapters()` (`adapters.js`) and used to _obtain_"]},"column":{"s":16,"e":34}},"dim":["","paragraph.201","inlineCode.3"],"code":"`createAdapters()`"},{"id":"/root/children/201/children/4","type":"text","loc":{"start":37025,"end":37027,"line":{"s":1037,"e":1037,"code":["eval context by `createAdapters()` (`adapters.js`) and used to _obtain_"]},"column":{"s":34,"e":36}},"dim":["","paragraph.201","text.4"],"code":" ("},{"id":"/root/children/201/children/5","type":"inlineCode","loc":{"start":37027,"end":37040,"line":{"s":1037,"e":1037,"code":["eval context by `createAdapters()` (`adapters.js`) and used to _obtain_"]},"column":{"s":36,"e":49}},"dim":["","paragraph.201","inlineCode.5"],"code":"`adapters.js`"},{"id":"/root/children/201/children/6","type":"text","loc":{"start":37040,"end":37054,"line":{"s":1037,"e":1037,"code":["eval context by `createAdapters()` (`adapters.js`) and used to _obtain_"]},"column":{"s":49,"e":63}},"dim":["","paragraph.201","text.6"],"code":") and used to "},{"id":"/root/children/201/children/7","type":"emphasis","loc":{"start":37054,"end":37062,"line":{"s":1037,"e":1037,"code":["eval context by `createAdapters()` (`adapters.js`) and used to _obtain_"]},"column":{"s":63,"e":71}},"dim":["","paragraph.201","emphasis.7"],"code":"_obtain_"},{"id":"/root/children/201/children/7/children/0","type":"text","loc":{"start":37055,"end":37061,"line":{"s":1037,"e":1037,"code":["eval context by `createAdapters()` (`adapters.js`) and used to _obtain_"]},"column":{"s":64,"e":70}},"dim":["","paragraph.201","emphasis.7","text.0"],"code":"obtain"},{"id":"/root/children/201/children/8","type":"text","loc":{"start":37062,"end":37080,"line":{"s":1037,"e":1038,"code":["eval context by `createAdapters()` (`adapters.js`) and used to _obtain_","items, which the `insert*` verbs then render. All three are `await`-ed."]},"column":{"s":71,"e":17}},"dim":["","paragraph.201","text.8"],"code":"\nitems, which the "},{"id":"/root/children/201/children/9","type":"inlineCode","loc":{"start":37080,"end":37089,"line":{"s":1038,"e":1038,"code":["items, which the `insert*` verbs then render. All three are `await`-ed."]},"column":{"s":17,"e":26}},"dim":["","paragraph.201","inlineCode.9"],"code":"`insert*`"},{"id":"/root/children/201/children/10","type":"text","loc":{"start":37089,"end":37123,"line":{"s":1038,"e":1038,"code":["items, which the `insert*` verbs then render. All three are `await`-ed."]},"column":{"s":26,"e":60}},"dim":["","paragraph.201","text.10"],"code":" verbs then render. All three are "},{"id":"/root/children/201/children/11","type":"inlineCode","loc":{"start":37123,"end":37130,"line":{"s":1038,"e":1038,"code":["items, which the `insert*` verbs then render. All three are `await`-ed."]},"column":{"s":60,"e":67}},"dim":["","paragraph.201","inlineCode.11"],"code":"`await`"},{"id":"/root/children/201/children/12","type":"text","loc":{"start":37130,"end":37134,"line":{"s":1038,"e":1038,"code":["items, which the `insert*` verbs then render. All three are `await`-ed."]},"column":{"s":67,"e":71}},"dim":["","paragraph.201","text.12"],"code":"-ed."},{"id":"/root/children/202","type":"paragraph","loc":{"start":37136,"end":37570,"line":{"s":1040,"e":1044,"code":["| Adapter              | Input                 | Returns                             |","| -------------------- | --------------------- | ----------------------------------- |","| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |","| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |","| `votesAsRefs(votes)` | vote rows             | ref items                           |"]},"column":{"s":0,"e":86}},"dim":["","paragraph.202"],"code":"| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |"},{"id":"/root/children/202/children/0","type":"text","loc":{"start":37136,"end":37312,"line":{"s":1040,"e":1042,"code":["| Adapter              | Input                 | Returns                             |","| -------------------- | --------------------- | ----------------------------------- |","| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.202","text.0"],"code":"| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| "},{"id":"/root/children/202/children/1","type":"inlineCode","loc":{"start":37312,"end":37327,"line":{"s":1042,"e":1042,"code":["| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |"]},"column":{"s":2,"e":17}},"dim":["","paragraph.202","inlineCode.1"],"code":"`search(query)`"},{"id":"/root/children/202/children/2","type":"text","loc":{"start":37327,"end":37370,"line":{"s":1042,"e":1042,"code":["| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |"]},"column":{"s":17,"e":60}},"dim":["","paragraph.202","text.2"],"code":"      | glass-search string   | ref items ("},{"id":"/root/children/202/children/3","type":"inlineCode","loc":{"start":37370,"end":37381,"line":{"s":1042,"e":1042,"code":["| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |"]},"column":{"s":60,"e":71}},"dim":["","paragraph.202","inlineCode.3"],"code":"`fragments`"},{"id":"/root/children/202/children/4","type":"text","loc":{"start":37381,"end":37383,"line":{"s":1042,"e":1042,"code":["| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |"]},"column":{"s":71,"e":73}},"dim":["","paragraph.202","text.4"],"code":", "},{"id":"/root/children/202/children/5","type":"inlineCode","loc":{"start":37383,"end":37390,"line":{"s":1042,"e":1042,"code":["| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |"]},"column":{"s":73,"e":80}},"dim":["","paragraph.202","inlineCode.5"],"code":"`files`"},{"id":"/root/children/202/children/6","type":"text","loc":{"start":37390,"end":37399,"line":{"s":1042,"e":1043,"code":["| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |","| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |"]},"column":{"s":80,"e":2}},"dim":["","paragraph.202","text.6"],"code":", …) |\n| "},{"id":"/root/children/202/children/7","type":"inlineCode","loc":{"start":37399,"end":37419,"line":{"s":1043,"e":1043,"code":["| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |"]},"column":{"s":2,"e":22}},"dim":["","paragraph.202","inlineCode.7"],"code":"`searchVotes(query)`"},{"id":"/root/children/202/children/8","type":"text","loc":{"start":37419,"end":37422,"line":{"s":1043,"e":1043,"code":["| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |"]},"column":{"s":22,"e":25}},"dim":["","paragraph.202","text.8"],"code":" | "},{"id":"/root/children/202/children/9","type":"inlineCode","loc":{"start":37422,"end":37443,"line":{"s":1043,"e":1043,"code":["| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |"]},"column":{"s":25,"e":46}},"dim":["","paragraph.202","inlineCode.9"],"code":"`{ campaign, repo? }`"},{"id":"/root/children/202/children/10","type":"text","loc":{"start":37443,"end":37461,"line":{"s":1043,"e":1043,"code":["| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |"]},"column":{"s":46,"e":64}},"dim":["","paragraph.202","text.10"],"code":" | vote rows from "},{"id":"/root/children/202/children/11","type":"inlineCode","loc":{"start":37461,"end":37480,"line":{"s":1043,"e":1043,"code":["| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |"]},"column":{"s":64,"e":83}},"dim":["","paragraph.202","inlineCode.11"],"code":"`v_voting_campaign`"},{"id":"/root/children/202/children/12","type":"text","loc":{"start":37480,"end":37486,"line":{"s":1043,"e":1044,"code":["| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |","| `votesAsRefs(votes)` | vote rows             | ref items                           |"]},"column":{"s":83,"e":2}},"dim":["","paragraph.202","text.12"],"code":"  |\n| "},{"id":"/root/children/202/children/13","type":"inlineCode","loc":{"start":37486,"end":37506,"line":{"s":1044,"e":1044,"code":["| `votesAsRefs(votes)` | vote rows             | ref items                           |"]},"column":{"s":2,"e":22}},"dim":["","paragraph.202","inlineCode.13"],"code":"`votesAsRefs(votes)`"},{"id":"/root/children/202/children/14","type":"text","loc":{"start":37506,"end":37570,"line":{"s":1044,"e":1044,"code":["| `votesAsRefs(votes)` | vote rows             | ref items                           |"]},"column":{"s":22,"e":86}},"dim":["","paragraph.202","text.14"],"code":" | vote rows             | ref items                           |"},{"id":"/root/children/203","type":"paragraph","loc":{"start":37572,"end":37781,"line":{"s":1046,"e":1048,"code":["`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to","`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an","array of prefixes — matching is by **prefix, not exact name**:"]},"column":{"s":0,"e":62}},"dim":["","paragraph.203"],"code":"`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:"},{"id":"/root/children/203/children/0","type":"inlineCode","loc":{"start":37572,"end":37585,"line":{"s":1046,"e":1046,"code":["`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to"]},"column":{"s":0,"e":13}},"dim":["","paragraph.203","inlineCode.0"],"code":"`searchVotes`"},{"id":"/root/children/203/children/1","type":"text","loc":{"start":37585,"end":37598,"line":{"s":1046,"e":1046,"code":["`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to"]},"column":{"s":13,"e":26}},"dim":["","paragraph.203","text.1"],"code":" queries the "},{"id":"/root/children/203/children/2","type":"inlineCode","loc":{"start":37598,"end":37617,"line":{"s":1046,"e":1046,"code":["`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to"]},"column":{"s":26,"e":45}},"dim":["","paragraph.203","inlineCode.2"],"code":"`v_voting_campaign`"},{"id":"/root/children/203/children/3","type":"text","loc":{"start":37617,"end":37624,"line":{"s":1046,"e":1046,"code":["`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to"]},"column":{"s":45,"e":52}},"dim":["","paragraph.203","text.3"],"code":" view. "},{"id":"/root/children/203/children/4","type":"inlineCode","loc":{"start":37624,"end":37630,"line":{"s":1046,"e":1046,"code":["`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to"]},"column":{"s":52,"e":58}},"dim":["","paragraph.203","inlineCode.4"],"code":"`repo`"},{"id":"/root/children/203/children/5","type":"text","loc":{"start":37630,"end":37643,"line":{"s":1046,"e":1047,"code":["`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to","`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an"]},"column":{"s":58,"e":0}},"dim":["","paragraph.203","text.5"],"code":" defaults to\n"},{"id":"/root/children/203/children/6","type":"inlineCode","loc":{"start":37643,"end":37659,"line":{"s":1047,"e":1047,"code":["`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an"]},"column":{"s":0,"e":16}},"dim":["","paragraph.203","inlineCode.6"],"code":"`STATE.repoName`"},{"id":"/root/children/203/children/7","type":"text","loc":{"start":37659,"end":37661,"line":{"s":1047,"e":1047,"code":["`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an"]},"column":{"s":16,"e":18}},"dim":["","paragraph.203","text.7"],"code":". "},{"id":"/root/children/203/children/8","type":"inlineCode","loc":{"start":37661,"end":37671,"line":{"s":1047,"e":1047,"code":["`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an"]},"column":{"s":18,"e":28}},"dim":["","paragraph.203","inlineCode.8"],"code":"`campaign`"},{"id":"/root/children/203/children/9","type":"text","loc":{"start":37671,"end":37680,"line":{"s":1047,"e":1047,"code":["`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an"]},"column":{"s":28,"e":37}},"dim":["","paragraph.203","text.9"],"code":" accepts "},{"id":"/root/children/203/children/10","type":"inlineCode","loc":{"start":37680,"end":37685,"line":{"s":1047,"e":1047,"code":["`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an"]},"column":{"s":37,"e":42}},"dim":["","paragraph.203","inlineCode.10"],"code":"`'*'`"},{"id":"/root/children/203/children/11","type":"text","loc":{"start":37685,"end":37754,"line":{"s":1047,"e":1048,"code":["`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an","array of prefixes — matching is by **prefix, not exact name**:"]},"column":{"s":42,"e":35}},"dim":["","paragraph.203","text.11"],"code":" (all campaigns), a prefix, or an\narray of prefixes — matching is by "},{"id":"/root/children/203/children/12","type":"strong","loc":{"start":37754,"end":37780,"line":{"s":1048,"e":1048,"code":["array of prefixes — matching is by **prefix, not exact name**:"]},"column":{"s":35,"e":61}},"dim":["","paragraph.203","strong.12"],"code":"**prefix, not exact name**"},{"id":"/root/children/203/children/12/children/0","type":"text","loc":{"start":37756,"end":37778,"line":{"s":1048,"e":1048,"code":["array of prefixes — matching is by **prefix, not exact name**:"]},"column":{"s":37,"e":59}},"dim":["","paragraph.203","strong.12","text.0"],"code":"prefix, not exact name"},{"id":"/root/children/203/children/13","type":"text","loc":{"start":37780,"end":37781,"line":{"s":1048,"e":1048,"code":["array of prefixes — matching is by **prefix, not exact name**:"]},"column":{"s":61,"e":62}},"dim":["","paragraph.203","text.13"],"code":":"},{"id":"/root/children/204","type":"paragraph","loc":{"start":37783,"end":38190,"line":{"s":1050,"e":1055,"code":["| `campaign`   | SQL condition                                    |","| ------------ | ------------------------------------------------ |","| `'*'`        | `1` — no filter                                  |","| `'do'`       | `campaign GLOB 'do:*'`                           |","| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |","| `[]`         | none — returns `[]` without querying             |"]},"column":{"s":0,"e":67}},"dim":["","paragraph.204"],"code":"| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |"},{"id":"/root/children/204/children/0","type":"text","loc":{"start":37783,"end":37785,"line":{"s":1050,"e":1050,"code":["| `campaign`   | SQL condition                                    |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.204","text.0"],"code":"| "},{"id":"/root/children/204/children/1","type":"inlineCode","loc":{"start":37785,"end":37795,"line":{"s":1050,"e":1050,"code":["| `campaign`   | SQL condition                                    |"]},"column":{"s":2,"e":12}},"dim":["","paragraph.204","inlineCode.1"],"code":"`campaign`"},{"id":"/root/children/204/children/2","type":"text","loc":{"start":37795,"end":37921,"line":{"s":1050,"e":1052,"code":["| `campaign`   | SQL condition                                    |","| ------------ | ------------------------------------------------ |","| `'*'`        | `1` — no filter                                  |"]},"column":{"s":12,"e":2}},"dim":["","paragraph.204","text.2"],"code":"   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| "},{"id":"/root/children/204/children/3","type":"inlineCode","loc":{"start":37921,"end":37926,"line":{"s":1052,"e":1052,"code":["| `'*'`        | `1` — no filter                                  |"]},"column":{"s":2,"e":7}},"dim":["","paragraph.204","inlineCode.3"],"code":"`'*'`"},{"id":"/root/children/204/children/4","type":"text","loc":{"start":37926,"end":37936,"line":{"s":1052,"e":1052,"code":["| `'*'`        | `1` — no filter                                  |"]},"column":{"s":7,"e":17}},"dim":["","paragraph.204","text.4"],"code":"        | "},{"id":"/root/children/204/children/5","type":"inlineCode","loc":{"start":37936,"end":37939,"line":{"s":1052,"e":1052,"code":["| `'*'`        | `1` — no filter                                  |"]},"column":{"s":17,"e":20}},"dim":["","paragraph.204","inlineCode.5"],"code":"`1`"},{"id":"/root/children/204/children/6","type":"text","loc":{"start":37939,"end":37989,"line":{"s":1052,"e":1053,"code":["| `'*'`        | `1` — no filter                                  |","| `'do'`       | `campaign GLOB 'do:*'`                           |"]},"column":{"s":20,"e":2}},"dim":["","paragraph.204","text.6"],"code":" — no filter                                  |\n| "},{"id":"/root/children/204/children/7","type":"inlineCode","loc":{"start":37989,"end":37995,"line":{"s":1053,"e":1053,"code":["| `'do'`       | `campaign GLOB 'do:*'`                           |"]},"column":{"s":2,"e":8}},"dim":["","paragraph.204","inlineCode.7"],"code":"`'do'`"},{"id":"/root/children/204/children/8","type":"text","loc":{"start":37995,"end":38004,"line":{"s":1053,"e":1053,"code":["| `'do'`       | `campaign GLOB 'do:*'`                           |"]},"column":{"s":8,"e":17}},"dim":["","paragraph.204","text.8"],"code":"       | "},{"id":"/root/children/204/children/9","type":"inlineCode","loc":{"start":38004,"end":38026,"line":{"s":1053,"e":1053,"code":["| `'do'`       | `campaign GLOB 'do:*'`                           |"]},"column":{"s":17,"e":39}},"dim":["","paragraph.204","inlineCode.9"],"code":"`campaign GLOB 'do:*'`"},{"id":"/root/children/204/children/10","type":"text","loc":{"start":38026,"end":38057,"line":{"s":1053,"e":1054,"code":["| `'do'`       | `campaign GLOB 'do:*'`                           |","| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |"]},"column":{"s":39,"e":2}},"dim":["","paragraph.204","text.10"],"code":"                           |\n| "},{"id":"/root/children/204/children/11","type":"inlineCode","loc":{"start":38057,"end":38069,"line":{"s":1054,"e":1054,"code":["| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |"]},"column":{"s":2,"e":14}},"dim":["","paragraph.204","inlineCode.11"],"code":"`['a', 'b']`"},{"id":"/root/children/204/children/12","type":"text","loc":{"start":38069,"end":38072,"line":{"s":1054,"e":1054,"code":["| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |"]},"column":{"s":14,"e":17}},"dim":["","paragraph.204","text.12"],"code":" | "},{"id":"/root/children/204/children/13","type":"inlineCode","loc":{"start":38072,"end":38120,"line":{"s":1054,"e":1054,"code":["| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |"]},"column":{"s":17,"e":65}},"dim":["","paragraph.204","inlineCode.13"],"code":"`( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )`"},{"id":"/root/children/204/children/14","type":"text","loc":{"start":38120,"end":38125,"line":{"s":1054,"e":1055,"code":["| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |","| `[]`         | none — returns `[]` without querying             |"]},"column":{"s":65,"e":2}},"dim":["","paragraph.204","text.14"],"code":" |\n| "},{"id":"/root/children/204/children/15","type":"inlineCode","loc":{"start":38125,"end":38129,"line":{"s":1055,"e":1055,"code":["| `[]`         | none — returns `[]` without querying             |"]},"column":{"s":2,"e":6}},"dim":["","paragraph.204","inlineCode.15"],"code":"`[]`"},{"id":"/root/children/204/children/16","type":"text","loc":{"start":38129,"end":38155,"line":{"s":1055,"e":1055,"code":["| `[]`         | none — returns `[]` without querying             |"]},"column":{"s":6,"e":32}},"dim":["","paragraph.204","text.16"],"code":"         | none — returns "},{"id":"/root/children/204/children/17","type":"inlineCode","loc":{"start":38155,"end":38159,"line":{"s":1055,"e":1055,"code":["| `[]`         | none — returns `[]` without querying             |"]},"column":{"s":32,"e":36}},"dim":["","paragraph.204","inlineCode.17"],"code":"`[]`"},{"id":"/root/children/204/children/18","type":"text","loc":{"start":38159,"end":38190,"line":{"s":1055,"e":1055,"code":["| `[]`         | none — returns `[]` without querying             |"]},"column":{"s":36,"e":67}},"dim":["","paragraph.204","text.18"],"code":" without querying             |"},{"id":"/root/children/205","type":"paragraph","loc":{"start":38192,"end":38409,"line":{"s":1057,"e":1059,"code":["This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence","worth remembering: an exact campaign name matches only if something sits","below it, so pass the parent prefix rather than the full campaign."]},"column":{"s":0,"e":66}},"dim":["","paragraph.205"],"code":"This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign."},{"id":"/root/children/205/children/0","type":"text","loc":{"start":38192,"end":38205,"line":{"s":1057,"e":1057,"code":["This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence"]},"column":{"s":0,"e":13}},"dim":["","paragraph.205","text.0"],"code":"This mirrors "},{"id":"/root/children/205/children/1","type":"inlineCode","loc":{"start":38205,"end":38221,"line":{"s":1057,"e":1057,"code":["This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence"]},"column":{"s":13,"e":29}},"dim":["","paragraph.205","inlineCode.1"],"code":"`campaignPrefix`"},{"id":"/root/children/205/children/2","type":"text","loc":{"start":38221,"end":38225,"line":{"s":1057,"e":1057,"code":["This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence"]},"column":{"s":29,"e":33}},"dim":["","paragraph.205","text.2"],"code":" in "},{"id":"/root/children/205/children/3","type":"inlineCode","loc":{"start":38225,"end":38254,"line":{"s":1057,"e":1057,"code":["This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence"]},"column":{"s":33,"e":62}},"dim":["","paragraph.205","inlineCode.3"],"code":"`tagCloudByVotingsFromView()`"},{"id":"/root/children/205/children/4","type":"text","loc":{"start":38254,"end":38409,"line":{"s":1057,"e":1059,"code":["This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence","worth remembering: an exact campaign name matches only if something sits","below it, so pass the parent prefix rather than the full campaign."]},"column":{"s":62,"e":66}},"dim":["","paragraph.205","text.4"],"code":". A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign."},{"id":"/root/children/206","type":"paragraph","loc":{"start":38411,"end":38437,"line":{"s":1061,"e":1061,"code":["Rows come back as objects:"]},"column":{"s":0,"e":26}},"dim":["","paragraph.206"],"code":"Rows come back as objects:"},{"id":"/root/children/206/children/0","type":"text","loc":{"start":38411,"end":38437,"line":{"s":1061,"e":1061,"code":["Rows come back as objects:"]},"column":{"s":0,"e":26}},"dim":["","paragraph.206","text.0"],"code":"Rows come back as objects:"},{"id":"/root/children/207","type":"code","loc":{"start":38440,"end":38506,"line":{"s":1064,"e":1066,"code":["```","repo campaign nomen aliasRef id num1 voteCount maxCount rn","```"]},"column":{"s":0,"e":3}},"dim":["","code.207"],"code":"```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```","symbName":"code","symbRange":[38508,38766],"symbRangeL":[null,1072],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>"},{"id":"/root/children/208","type":"paragraph","loc":{"start":38508,"end":38766,"line":{"s":1068,"e":1071,"code":["`score` is **not** selected — the deployed view may have been generated with","`withScore: false`, and its `LN()` also needs a SQLite built with","`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from","`voteCount / maxCount`, and added to each row:"]},"column":{"s":0,"e":46}},"dim":["","paragraph.208"],"code":"`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:"},{"id":"/root/children/208/children/0","type":"inlineCode","loc":{"start":38508,"end":38515,"line":{"s":1068,"e":1068,"code":["`score` is **not** selected — the deployed view may have been generated with"]},"column":{"s":0,"e":7}},"dim":["","paragraph.208","inlineCode.0"],"code":"`score`"},{"id":"/root/children/208/children/1","type":"text","loc":{"start":38515,"end":38519,"line":{"s":1068,"e":1068,"code":["`score` is **not** selected — the deployed view may have been generated with"]},"column":{"s":7,"e":11}},"dim":["","paragraph.208","text.1"],"code":" is "},{"id":"/root/children/208/children/2","type":"strong","loc":{"start":38519,"end":38526,"line":{"s":1068,"e":1068,"code":["`score` is **not** selected — the deployed view may have been generated with"]},"column":{"s":11,"e":18}},"dim":["","paragraph.208","strong.2"],"code":"**not**"},{"id":"/root/children/208/children/2/children/0","type":"text","loc":{"start":38521,"end":38524,"line":{"s":1068,"e":1068,"code":["`score` is **not** selected — the deployed view may have been generated with"]},"column":{"s":13,"e":16}},"dim":["","paragraph.208","strong.2","text.0"],"code":"not"},{"id":"/root/children/208/children/3","type":"text","loc":{"start":38526,"end":38585,"line":{"s":1068,"e":1069,"code":["`score` is **not** selected — the deployed view may have been generated with","`withScore: false`, and its `LN()` also needs a SQLite built with"]},"column":{"s":18,"e":0}},"dim":["","paragraph.208","text.3"],"code":" selected — the deployed view may have been generated with\n"},{"id":"/root/children/208/children/4","type":"inlineCode","loc":{"start":38585,"end":38603,"line":{"s":1069,"e":1069,"code":["`withScore: false`, and its `LN()` also needs a SQLite built with"]},"column":{"s":0,"e":18}},"dim":["","paragraph.208","inlineCode.4"],"code":"`withScore: false`"},{"id":"/root/children/208/children/5","type":"text","loc":{"start":38603,"end":38613,"line":{"s":1069,"e":1069,"code":["`withScore: false`, and its `LN()` also needs a SQLite built with"]},"column":{"s":18,"e":28}},"dim":["","paragraph.208","text.5"],"code":", and its "},{"id":"/root/children/208/children/6","type":"inlineCode","loc":{"start":38613,"end":38619,"line":{"s":1069,"e":1069,"code":["`withScore: false`, and its `LN()` also needs a SQLite built with"]},"column":{"s":28,"e":34}},"dim":["","paragraph.208","inlineCode.6"],"code":"`LN()`"},{"id":"/root/children/208/children/7","type":"text","loc":{"start":38619,"end":38651,"line":{"s":1069,"e":1070,"code":["`withScore: false`, and its `LN()` also needs a SQLite built with","`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from"]},"column":{"s":34,"e":0}},"dim":["","paragraph.208","text.7"],"code":" also needs a SQLite built with\n"},{"id":"/root/children/208/children/8","type":"inlineCode","loc":{"start":38651,"end":38681,"line":{"s":1070,"e":1070,"code":["`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from"]},"column":{"s":0,"e":30}},"dim":["","paragraph.208","inlineCode.8"],"code":"`SQLITE_ENABLE_MATH_FUNCTIONS`"},{"id":"/root/children/208/children/9","type":"text","loc":{"start":38681,"end":38720,"line":{"s":1070,"e":1071,"code":["`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from","`voteCount / maxCount`, and added to each row:"]},"column":{"s":30,"e":0}},"dim":["","paragraph.208","text.9"],"code":". It is computed locally instead, from\n"},{"id":"/root/children/208/children/10","type":"inlineCode","loc":{"start":38720,"end":38742,"line":{"s":1071,"e":1071,"code":["`voteCount / maxCount`, and added to each row:"]},"column":{"s":0,"e":22}},"dim":["","paragraph.208","inlineCode.10"],"code":"`voteCount / maxCount`"},{"id":"/root/children/208/children/11","type":"text","loc":{"start":38742,"end":38766,"line":{"s":1071,"e":1071,"code":["`voteCount / maxCount`, and added to each row:"]},"column":{"s":22,"e":46}},"dim":["","paragraph.208","text.11"],"code":", and added to each row:"},{"id":"/root/children/209","type":"code","loc":{"start":38768,"end":38835,"line":{"s":1073,"e":1075,"code":["```js","1 + Math.round(Math.log1p((voteCount / maxCount) * 100));","```"]},"column":{"s":0,"e":3}},"dim":["","code.209"],"code":"```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```","symbName":"code","symbRange":[38837,39592],"symbRangeL":[null,1092],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n"},{"id":"/root/children/210","type":"paragraph","loc":{"start":38837,"end":38911,"line":{"s":1077,"e":1077,"code":["Verified identical to the view's SQL expression across the real vote rows."]},"column":{"s":0,"e":74}},"dim":["","paragraph.210"],"code":"Verified identical to the view's SQL expression across the real vote rows."},{"id":"/root/children/210/children/0","type":"text","loc":{"start":38837,"end":38911,"line":{"s":1077,"e":1077,"code":["Verified identical to the view's SQL expression across the real vote rows."]},"column":{"s":0,"e":74}},"dim":["","paragraph.210","text.0"],"code":"Verified identical to the view's SQL expression across the real vote rows."},{"id":"/root/children/211","type":"paragraph","loc":{"start":38913,"end":39412,"line":{"s":1079,"e":1085,"code":["`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and","`num1`, which is everything a ref item needs. It builds `uri` the same way a","`fragments` search does (`#/paper/${aliasRef}`, falling back to","`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping","the `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data","(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so","`insertRefsAsNljson` can surface counts without a second query."]},"column":{"s":0,"e":63}},"dim":["","paragraph.211"],"code":"`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query."},{"id":"/root/children/211/children/0","type":"inlineCode","loc":{"start":38913,"end":38926,"line":{"s":1079,"e":1079,"code":["`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and"]},"column":{"s":0,"e":13}},"dim":["","paragraph.211","inlineCode.0"],"code":"`votesAsRefs`"},{"id":"/root/children/211/children/1","type":"text","loc":{"start":38926,"end":38966,"line":{"s":1079,"e":1079,"code":["`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and"]},"column":{"s":13,"e":53}},"dim":["","paragraph.211","text.1"],"code":" is a pure conversion — vote rows carry "},{"id":"/root/children/211/children/2","type":"inlineCode","loc":{"start":38966,"end":38976,"line":{"s":1079,"e":1079,"code":["`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and"]},"column":{"s":53,"e":63}},"dim":["","paragraph.211","inlineCode.2"],"code":"`aliasRef`"},{"id":"/root/children/211/children/3","type":"text","loc":{"start":38976,"end":38978,"line":{"s":1079,"e":1079,"code":["`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and"]},"column":{"s":63,"e":65}},"dim":["","paragraph.211","text.3"],"code":", "},{"id":"/root/children/211/children/4","type":"inlineCode","loc":{"start":38978,"end":38982,"line":{"s":1079,"e":1079,"code":["`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and"]},"column":{"s":65,"e":69}},"dim":["","paragraph.211","inlineCode.4"],"code":"`id`"},{"id":"/root/children/211/children/5","type":"text","loc":{"start":38982,"end":38987,"line":{"s":1079,"e":1080,"code":["`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and","`num1`, which is everything a ref item needs. It builds `uri` the same way a"]},"column":{"s":69,"e":0}},"dim":["","paragraph.211","text.5"],"code":" and\n"},{"id":"/root/children/211/children/6","type":"inlineCode","loc":{"start":38987,"end":38993,"line":{"s":1080,"e":1080,"code":["`num1`, which is everything a ref item needs. It builds `uri` the same way a"]},"column":{"s":0,"e":6}},"dim":["","paragraph.211","inlineCode.6"],"code":"`num1`"},{"id":"/root/children/211/children/7","type":"text","loc":{"start":38993,"end":39043,"line":{"s":1080,"e":1080,"code":["`num1`, which is everything a ref item needs. It builds `uri` the same way a"]},"column":{"s":6,"e":56}},"dim":["","paragraph.211","text.7"],"code":", which is everything a ref item needs. It builds "},{"id":"/root/children/211/children/8","type":"inlineCode","loc":{"start":39043,"end":39048,"line":{"s":1080,"e":1080,"code":["`num1`, which is everything a ref item needs. It builds `uri` the same way a"]},"column":{"s":56,"e":61}},"dim":["","paragraph.211","inlineCode.8"],"code":"`uri`"},{"id":"/root/children/211/children/9","type":"text","loc":{"start":39048,"end":39064,"line":{"s":1080,"e":1081,"code":["`num1`, which is everything a ref item needs. It builds `uri` the same way a","`fragments` search does (`#/paper/${aliasRef}`, falling back to"]},"column":{"s":61,"e":0}},"dim":["","paragraph.211","text.9"],"code":" the same way a\n"},{"id":"/root/children/211/children/10","type":"inlineCode","loc":{"start":39064,"end":39075,"line":{"s":1081,"e":1081,"code":["`fragments` search does (`#/paper/${aliasRef}`, falling back to"]},"column":{"s":0,"e":11}},"dim":["","paragraph.211","inlineCode.10"],"code":"`fragments`"},{"id":"/root/children/211/children/11","type":"text","loc":{"start":39075,"end":39089,"line":{"s":1081,"e":1081,"code":["`fragments` search does (`#/paper/${aliasRef}`, falling back to"]},"column":{"s":11,"e":25}},"dim":["","paragraph.211","text.11"],"code":" search does ("},{"id":"/root/children/211/children/12","type":"inlineCode","loc":{"start":39089,"end":39110,"line":{"s":1081,"e":1081,"code":["`fragments` search does (`#/paper/${aliasRef}`, falling back to"]},"column":{"s":25,"e":46}},"dim":["","paragraph.211","inlineCode.12"],"code":"`#/paper/${aliasRef}`"},{"id":"/root/children/211/children/13","type":"text","loc":{"start":39110,"end":39128,"line":{"s":1081,"e":1082,"code":["`fragments` search does (`#/paper/${aliasRef}`, falling back to","`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping"]},"column":{"s":46,"e":0}},"dim":["","paragraph.211","text.13"],"code":", falling back to\n"},{"id":"/root/children/211/children/14","type":"inlineCode","loc":{"start":39128,"end":39144,"line":{"s":1082,"e":1082,"code":["`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping"]},"column":{"s":0,"e":16}},"dim":["","paragraph.211","inlineCode.14"],"code":"`legacyPaperUrl`"},{"id":"/root/children/211/children/15","type":"text","loc":{"start":39144,"end":39152,"line":{"s":1082,"e":1082,"code":["`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping"]},"column":{"s":16,"e":24}},"dim":["","paragraph.211","text.15"],"code":"), sets "},{"id":"/root/children/211/children/16","type":"inlineCode","loc":{"start":39152,"end":39159,"line":{"s":1082,"e":1082,"code":["`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping"]},"column":{"s":24,"e":31}},"dim":["","paragraph.211","inlineCode.16"],"code":"`nomen`"},{"id":"/root/children/211/children/17","type":"text","loc":{"start":39159,"end":39187,"line":{"s":1082,"e":1082,"code":["`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping"]},"column":{"s":31,"e":59}},"dim":["","paragraph.211","text.17"],"code":" for the label, and derives "},{"id":"/root/children/211/children/18","type":"inlineCode","loc":{"start":39187,"end":39191,"line":{"s":1082,"e":1082,"code":["`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping"]},"column":{"s":59,"e":63}},"dim":["","paragraph.211","inlineCode.18"],"code":"`fn`"},{"id":"/root/children/211/children/19","type":"text","loc":{"start":39191,"end":39209,"line":{"s":1082,"e":1083,"code":["`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping","the `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data"]},"column":{"s":63,"e":4}},"dim":["","paragraph.211","text.19"],"code":" by stripping\nthe "},{"id":"/root/children/211/children/20","type":"inlineCode","loc":{"start":39209,"end":39216,"line":{"s":1083,"e":1083,"code":["the `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data"]},"column":{"s":4,"e":11}},"dim":["","paragraph.211","inlineCode.20"],"code":"`:NNNN`"},{"id":"/root/children/211/children/21","type":"text","loc":{"start":39216,"end":39237,"line":{"s":1083,"e":1083,"code":["the `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data"]},"column":{"s":11,"e":32}},"dim":["","paragraph.211","text.21"],"code":" node-seq suffix off "},{"id":"/root/children/211/children/22","type":"inlineCode","loc":{"start":39237,"end":39241,"line":{"s":1083,"e":1083,"code":["the `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data"]},"column":{"s":32,"e":36}},"dim":["","paragraph.211","inlineCode.22"],"code":"`id`"},{"id":"/root/children/211/children/23","type":"text","loc":{"start":39241,"end":39245,"line":{"s":1083,"e":1083,"code":["the `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data"]},"column":{"s":36,"e":40}},"dim":["","paragraph.211","text.23"],"code":" so "},{"id":"/root/children/211/children/24","type":"inlineCode","loc":{"start":39245,"end":39259,"line":{"s":1083,"e":1083,"code":["the `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data"]},"column":{"s":40,"e":54}},"dim":["","paragraph.211","inlineCode.24"],"code":"`buildRefId()`"},{"id":"/root/children/211/children/25","type":"text","loc":{"start":39259,"end":39281,"line":{"s":1083,"e":1084,"code":["the `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data","(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so"]},"column":{"s":54,"e":1}},"dim":["","paragraph.211","text.25"],"code":" resolves. Vote data\n("},{"id":"/root/children/211/children/26","type":"inlineCode","loc":{"start":39281,"end":39291,"line":{"s":1084,"e":1084,"code":["(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so"]},"column":{"s":1,"e":11}},"dim":["","paragraph.211","inlineCode.26"],"code":"`campaign`"},{"id":"/root/children/211/children/27","type":"text","loc":{"start":39291,"end":39293,"line":{"s":1084,"e":1084,"code":["(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so"]},"column":{"s":11,"e":13}},"dim":["","paragraph.211","text.27"],"code":", "},{"id":"/root/children/211/children/28","type":"inlineCode","loc":{"start":39293,"end":39304,"line":{"s":1084,"e":1084,"code":["(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so"]},"column":{"s":13,"e":24}},"dim":["","paragraph.211","inlineCode.28"],"code":"`voteCount`"},{"id":"/root/children/211/children/29","type":"text","loc":{"start":39304,"end":39306,"line":{"s":1084,"e":1084,"code":["(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so"]},"column":{"s":24,"e":26}},"dim":["","paragraph.211","text.29"],"code":", "},{"id":"/root/children/211/children/30","type":"inlineCode","loc":{"start":39306,"end":39316,"line":{"s":1084,"e":1084,"code":["(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so"]},"column":{"s":26,"e":36}},"dim":["","paragraph.211","inlineCode.30"],"code":"`maxCount`"},{"id":"/root/children/211/children/31","type":"text","loc":{"start":39316,"end":39318,"line":{"s":1084,"e":1084,"code":["(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so"]},"column":{"s":36,"e":38}},"dim":["","paragraph.211","text.31"],"code":", "},{"id":"/root/children/211/children/32","type":"inlineCode","loc":{"start":39318,"end":39325,"line":{"s":1084,"e":1084,"code":["(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so"]},"column":{"s":38,"e":45}},"dim":["","paragraph.211","inlineCode.32"],"code":"`score`"},{"id":"/root/children/211/children/33","type":"text","loc":{"start":39325,"end":39327,"line":{"s":1084,"e":1084,"code":["(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so"]},"column":{"s":45,"e":47}},"dim":["","paragraph.211","text.33"],"code":", "},{"id":"/root/children/211/children/34","type":"inlineCode","loc":{"start":39327,"end":39331,"line":{"s":1084,"e":1084,"code":["(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so"]},"column":{"s":47,"e":51}},"dim":["","paragraph.211","inlineCode.34"],"code":"`rn`"},{"id":"/root/children/211/children/35","type":"text","loc":{"start":39331,"end":39349,"line":{"s":1084,"e":1085,"code":["(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so","`insertRefsAsNljson` can surface counts without a second query."]},"column":{"s":51,"e":0}},"dim":["","paragraph.211","text.35"],"code":") rides along, so\n"},{"id":"/root/children/211/children/36","type":"inlineCode","loc":{"start":39349,"end":39369,"line":{"s":1085,"e":1085,"code":["`insertRefsAsNljson` can surface counts without a second query."]},"column":{"s":0,"e":20}},"dim":["","paragraph.211","inlineCode.36"],"code":"`insertRefsAsNljson`"},{"id":"/root/children/211/children/37","type":"text","loc":{"start":39369,"end":39412,"line":{"s":1085,"e":1085,"code":["`insertRefsAsNljson` can surface counts without a second query."]},"column":{"s":20,"e":63}},"dim":["","paragraph.211","text.37"],"code":" can surface counts without a second query."},{"id":"/root/children/212","type":"paragraph","loc":{"start":39414,"end":39555,"line":{"s":1087,"e":1088,"code":["It is `async` despite doing no I/O today — the signature is the contract, so","a later version can enrich from the DB without breaking callers."]},"column":{"s":0,"e":64}},"dim":["","paragraph.212"],"code":"It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers."},{"id":"/root/children/212/children/0","type":"text","loc":{"start":39414,"end":39420,"line":{"s":1087,"e":1087,"code":["It is `async` despite doing no I/O today — the signature is the contract, so"]},"column":{"s":0,"e":6}},"dim":["","paragraph.212","text.0"],"code":"It is "},{"id":"/root/children/212/children/1","type":"inlineCode","loc":{"start":39420,"end":39427,"line":{"s":1087,"e":1087,"code":["It is `async` despite doing no I/O today — the signature is the contract, so"]},"column":{"s":6,"e":13}},"dim":["","paragraph.212","inlineCode.1"],"code":"`async`"},{"id":"/root/children/212/children/2","type":"text","loc":{"start":39427,"end":39555,"line":{"s":1087,"e":1088,"code":["It is `async` despite doing no I/O today — the signature is the contract, so","a later version can enrich from the DB without breaking callers."]},"column":{"s":13,"e":64}},"dim":["","paragraph.212","text.2"],"code":" despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers."},{"id":"/root/children/213","type":"paragraph","loc":{"start":39557,"end":39592,"line":{"s":1090,"e":1090,"code":["**Example — list voted fragments:**"]},"column":{"s":0,"e":35}},"dim":["","paragraph.213"],"code":"**Example — list voted fragments:**"},{"id":"/root/children/213/children/0","type":"strong","loc":{"start":39557,"end":39592,"line":{"s":1090,"e":1090,"code":["**Example — list voted fragments:**"]},"column":{"s":0,"e":35}},"dim":["","paragraph.213","strong.0"],"code":"**Example — list voted fragments:**"},{"id":"/root/children/213/children/0/children/0","type":"text","loc":{"start":39559,"end":39590,"line":{"s":1090,"e":1090,"code":["**Example — list voted fragments:**"]},"column":{"s":2,"e":33}},"dim":["","paragraph.213","strong.0","text.0"],"code":"Example — list voted fragments:"},{"id":"/root/children/214","type":"code","loc":{"start":39595,"end":39840,"line":{"s":1093,"e":1108,"code":["```md","## ${init}","","\\`\\`\\`javascript","mdtState.queryVotes = { campaign: '*' }","mdtState.votes = await searchVotes(mdtState.queryVotes)","\\`\\`\\`","","### ${list}","","\\`\\`\\`javascript","return [","  insertRefsAsList(await votesAsRefs(mdtState.votes)),","]","\\`\\`\\`","```"]},"column":{"s":0,"e":3}},"dim":["","code.214"],"code":"```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```","symbName":"code","symbRange":[39842,42584],"symbRangeL":[null,1153],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n"},{"id":"/root/children/215","type":"paragraph","loc":{"start":39842,"end":39958,"line":{"s":1110,"e":1111,"code":["Both are wired in `adapters.js` exactly as `search` is, so anything that","builds a runner context gets them for free."]},"column":{"s":0,"e":43}},"dim":["","paragraph.215"],"code":"Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free."},{"id":"/root/children/215/children/0","type":"text","loc":{"start":39842,"end":39860,"line":{"s":1110,"e":1110,"code":["Both are wired in `adapters.js` exactly as `search` is, so anything that"]},"column":{"s":0,"e":18}},"dim":["","paragraph.215","text.0"],"code":"Both are wired in "},{"id":"/root/children/215/children/1","type":"inlineCode","loc":{"start":39860,"end":39873,"line":{"s":1110,"e":1110,"code":["Both are wired in `adapters.js` exactly as `search` is, so anything that"]},"column":{"s":18,"e":31}},"dim":["","paragraph.215","inlineCode.1"],"code":"`adapters.js`"},{"id":"/root/children/215/children/2","type":"text","loc":{"start":39873,"end":39885,"line":{"s":1110,"e":1110,"code":["Both are wired in `adapters.js` exactly as `search` is, so anything that"]},"column":{"s":31,"e":43}},"dim":["","paragraph.215","text.2"],"code":" exactly as "},{"id":"/root/children/215/children/3","type":"inlineCode","loc":{"start":39885,"end":39893,"line":{"s":1110,"e":1110,"code":["Both are wired in `adapters.js` exactly as `search` is, so anything that"]},"column":{"s":43,"e":51}},"dim":["","paragraph.215","inlineCode.3"],"code":"`search`"},{"id":"/root/children/215/children/4","type":"text","loc":{"start":39893,"end":39958,"line":{"s":1110,"e":1111,"code":["Both are wired in `adapters.js` exactly as `search` is, so anything that","builds a runner context gets them for free."]},"column":{"s":51,"e":43}},"dim":["","paragraph.215","text.4"],"code":" is, so anything that\nbuilds a runner context gets them for free."},{"id":"/root/children/216","type":"heading","loc":{"start":39960,"end":39993,"line":{"s":1113,"e":1113,"code":["#### Command contract — all verbs"]},"column":{"s":0,"e":33}},"dim":["","heading.216"],"code":"#### Command contract — all verbs","symbName":"heading","symbRange":[39995,43683],"symbRangeL":[1113,1180],"outerCode":"\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.","outerHtml":"\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>"},{"id":"/root/children/216/children/0","type":"text","loc":{"start":39965,"end":39993,"line":{"s":1113,"e":1113,"code":["#### Command contract — all verbs"]},"column":{"s":5,"e":33}},"dim":["","heading.216","text.0"],"code":"Command contract — all verbs"},{"id":"/root/children/217","type":"paragraph","loc":{"start":39995,"end":41026,"line":{"s":1115,"e":1122,"code":["| Helper                                 | Input      | Fragments            | Body                                            |","| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |","| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |","| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |","| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |","| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |","| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |","| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |"]},"column":{"s":0,"e":128}},"dim":["","paragraph.217"],"code":"| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |"},{"id":"/root/children/217/children/0","type":"text","loc":{"start":39995,"end":40255,"line":{"s":1115,"e":1117,"code":["| Helper                                 | Input      | Fragments            | Body                                            |","| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |","| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |"]},"column":{"s":0,"e":2}},"dim":["","paragraph.217","text.0"],"code":"| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| "},{"id":"/root/children/217/children/1","type":"inlineCode","loc":{"start":40255,"end":40273,"line":{"s":1117,"e":1117,"code":["| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |"]},"column":{"s":2,"e":20}},"dim":["","paragraph.217","inlineCode.1"],"code":"`insert(x, opts?)`"},{"id":"/root/children/217/children/2","type":"text","loc":{"start":40273,"end":40338,"line":{"s":1117,"e":1117,"code":["| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |"]},"column":{"s":20,"e":85}},"dim":["","paragraph.217","text.2"],"code":"                     | anything   | 1                    | array→"},{"id":"/root/children/217/children/3","type":"inlineCode","loc":{"start":40338,"end":40342,"line":{"s":1117,"e":1117,"code":["| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |"]},"column":{"s":85,"e":89}},"dim":["","paragraph.217","inlineCode.3"],"code":"`\\n`"},{"id":"/root/children/217/children/4","type":"text","loc":{"start":40342,"end":40369,"line":{"s":1117,"e":1117,"code":["| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |"]},"column":{"s":89,"e":116}},"dim":["","paragraph.217","text.4"],"code":"-joined, object→JSON, else "},{"id":"/root/children/217/children/5","type":"inlineCode","loc":{"start":40369,"end":40379,"line":{"s":1117,"e":1117,"code":["| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |"]},"column":{"s":116,"e":126}},"dim":["","paragraph.217","inlineCode.5"],"code":"`String()`"},{"id":"/root/children/217/children/6","type":"text","loc":{"start":40379,"end":40384,"line":{"s":1117,"e":1118,"code":["| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |","| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |"]},"column":{"s":126,"e":2}},"dim":["","paragraph.217","text.6"],"code":" |\n| "},{"id":"/root/children/217/children/7","type":"inlineCode","loc":{"start":40384,"end":40395,"line":{"s":1118,"e":1118,"code":["| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |"]},"column":{"s":2,"e":13}},"dim":["","paragraph.217","inlineCode.7"],"code":"`inject(s)`"},{"id":"/root/children/217/children/8","type":"text","loc":{"start":40395,"end":40425,"line":{"s":1118,"e":1118,"code":["| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |"]},"column":{"s":13,"e":43}},"dim":["","paragraph.217","text.8"],"code":"                            | "},{"id":"/root/children/217/children/9","type":"inlineCode","loc":{"start":40425,"end":40433,"line":{"s":1118,"e":1118,"code":["| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |"]},"column":{"s":43,"e":51}},"dim":["","paragraph.217","inlineCode.9"],"code":"`string`"},{"id":"/root/children/217/children/10","type":"text","loc":{"start":40433,"end":40513,"line":{"s":1118,"e":1119,"code":["| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |","| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |"]},"column":{"s":51,"e":2}},"dim":["","paragraph.217","text.10"],"code":"   | 1                    | raw passthrough, no heading, empty trail        |\n| "},{"id":"/root/children/217/children/11","type":"inlineCode","loc":{"start":40513,"end":40537,"line":{"s":1119,"e":1119,"code":["| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |"]},"column":{"s":2,"e":26}},"dim":["","paragraph.217","inlineCode.11"],"code":"`insertNljson(x, opts?)`"},{"id":"/root/children/217/children/12","type":"text","loc":{"start":40537,"end":40590,"line":{"s":1119,"e":1119,"code":["| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |"]},"column":{"s":26,"e":79}},"dim":["","paragraph.217","text.12"],"code":"               | collection | 1                    | "},{"id":"/root/children/217/children/13","type":"inlineCode","loc":{"start":40590,"end":40603,"line":{"s":1119,"e":1119,"code":["| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |"]},"column":{"s":79,"e":92}},"dim":["","paragraph.217","inlineCode.13"],"code":"` ```nljson `"},{"id":"/root/children/217/children/14","type":"text","loc":{"start":40603,"end":40642,"line":{"s":1119,"e":1120,"code":["| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |","| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |"]},"column":{"s":92,"e":2}},"dim":["","paragraph.217","text.14"],"code":" fence, one JSON per line          |\n| "},{"id":"/root/children/217/children/15","type":"inlineCode","loc":{"start":40642,"end":40674,"line":{"s":1120,"e":1120,"code":["| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |"]},"column":{"s":2,"e":34}},"dim":["","paragraph.217","inlineCode.15"],"code":"`insertRefsAsList(items, opts?)`"},{"id":"/root/children/217/children/16","type":"text","loc":{"start":40674,"end":40719,"line":{"s":1120,"e":1120,"code":["| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |"]},"column":{"s":34,"e":79}},"dim":["","paragraph.217","text.16"],"code":"       | ref items  | 1                    | "},{"id":"/root/children/217/children/17","type":"inlineCode","loc":{"start":40719,"end":40742,"line":{"s":1120,"e":1120,"code":["| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |"]},"column":{"s":79,"e":102}},"dim":["","paragraph.217","inlineCode.17"],"code":"`- [nomen](uri) {data}`"},{"id":"/root/children/217/children/18","type":"text","loc":{"start":40742,"end":40771,"line":{"s":1120,"e":1121,"code":["| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |","| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |"]},"column":{"s":102,"e":2}},"dim":["","paragraph.217","text.18"],"code":" bullet list             |\n| "},{"id":"/root/children/217/children/19","type":"inlineCode","loc":{"start":40771,"end":40809,"line":{"s":1121,"e":1121,"code":["| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |"]},"column":{"s":2,"e":40}},"dim":["","paragraph.217","inlineCode.19"],"code":"`insertRefsAsNljson(items, optsOrFn?)`"},{"id":"/root/children/217/children/20","type":"text","loc":{"start":40809,"end":40848,"line":{"s":1121,"e":1121,"code":["| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |"]},"column":{"s":40,"e":79}},"dim":["","paragraph.217","text.20"],"code":" | ref items  | 1                    | "},{"id":"/root/children/217/children/21","type":"inlineCode","loc":{"start":40848,"end":40861,"line":{"s":1121,"e":1121,"code":["| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |"]},"column":{"s":79,"e":92}},"dim":["","paragraph.217","inlineCode.21"],"code":"` ```nljson `"},{"id":"/root/children/217/children/22","type":"text","loc":{"start":40861,"end":40888,"line":{"s":1121,"e":1121,"code":["| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |"]},"column":{"s":92,"e":119}},"dim":["","paragraph.217","text.22"],"code":" fence, scalar cells, auto "},{"id":"/root/children/217/children/23","type":"inlineCode","loc":{"start":40888,"end":40894,"line":{"s":1121,"e":1121,"code":["| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |"]},"column":{"s":119,"e":125}},"dim":["","paragraph.217","inlineCode.23"],"code":"`link`"},{"id":"/root/children/217/children/24","type":"text","loc":{"start":40894,"end":40900,"line":{"s":1121,"e":1122,"code":["| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |","| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |"]},"column":{"s":125,"e":2}},"dim":["","paragraph.217","text.24"],"code":"  |\n| "},{"id":"/root/children/217/children/25","type":"inlineCode","loc":{"start":40900,"end":40935,"line":{"s":1122,"e":1122,"code":["| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |"]},"column":{"s":2,"e":37}},"dim":["","paragraph.217","inlineCode.25"],"code":"`insertRefsAsSubtree(items, opts?)`"},{"id":"/root/children/217/children/26","type":"text","loc":{"start":40935,"end":40954,"line":{"s":1122,"e":1122,"code":["| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |"]},"column":{"s":37,"e":56}},"dim":["","paragraph.217","text.26"],"code":"    | ref items  | "},{"id":"/root/children/217/children/27","type":"strong","loc":{"start":40954,"end":40959,"line":{"s":1122,"e":1122,"code":["| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |"]},"column":{"s":56,"e":61}},"dim":["","paragraph.217","strong.27"],"code":"**N**"},{"id":"/root/children/217/children/27/children/0","type":"text","loc":{"start":40956,"end":40957,"line":{"s":1122,"e":1122,"code":["| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |"]},"column":{"s":58,"e":59}},"dim":["","paragraph.217","strong.27","text.0"],"code":"N"},{"id":"/root/children/217/children/28","type":"text","loc":{"start":40959,"end":41014,"line":{"s":1122,"e":1122,"code":["| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |"]},"column":{"s":61,"e":116}},"dim":["","paragraph.217","text.28"],"code":" (one per item) | heading-only; body fetched lazily in "},{"id":"/root/children/217/children/29","type":"inlineCode","loc":{"start":41014,"end":41024,"line":{"s":1122,"e":1122,"code":["| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |"]},"column":{"s":116,"e":126}},"dim":["","paragraph.217","inlineCode.29"],"code":"`expand()`"},{"id":"/root/children/217/children/30","type":"text","loc":{"start":41024,"end":41026,"line":{"s":1122,"e":1122,"code":["| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |"]},"column":{"s":126,"e":128}},"dim":["","paragraph.217","text.30"],"code":" |"},{"id":"/root/children/218","type":"paragraph","loc":{"start":41028,"end":41152,"line":{"s":1124,"e":1125,"code":["`buildUrl(content, mimeType?)` is a helper, not a command — it returns a","`data:` URI string for use inside any of the above."]},"column":{"s":0,"e":51}},"dim":["","paragraph.218"],"code":"`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above."},{"id":"/root/children/218/children/0","type":"inlineCode","loc":{"start":41028,"end":41058,"line":{"s":1124,"e":1124,"code":["`buildUrl(content, mimeType?)` is a helper, not a command — it returns a"]},"column":{"s":0,"e":30}},"dim":["","paragraph.218","inlineCode.0"],"code":"`buildUrl(content, mimeType?)`"},{"id":"/root/children/218/children/1","type":"text","loc":{"start":41058,"end":41101,"line":{"s":1124,"e":1125,"code":["`buildUrl(content, mimeType?)` is a helper, not a command — it returns a","`data:` URI string for use inside any of the above."]},"column":{"s":30,"e":0}},"dim":["","paragraph.218","text.1"],"code":" is a helper, not a command — it returns a\n"},{"id":"/root/children/218/children/2","type":"inlineCode","loc":{"start":41101,"end":41108,"line":{"s":1125,"e":1125,"code":["`data:` URI string for use inside any of the above."]},"column":{"s":0,"e":7}},"dim":["","paragraph.218","inlineCode.2"],"code":"`data:`"},{"id":"/root/children/218/children/3","type":"text","loc":{"start":41108,"end":41152,"line":{"s":1125,"e":1125,"code":["`data:` URI string for use inside any of the above."]},"column":{"s":7,"e":51}},"dim":["","paragraph.218","text.3"],"code":" URI string for use inside any of the above."},{"id":"/root/children/219","type":"paragraph","loc":{"start":41154,"end":41650,"line":{"s":1127,"e":1133,"code":["**`insertRefsAsSubtree` is the structural odd one out.** Every other verb","yields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)","whose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out","to one Fragment _per item_, each with a real visible heading, `hasChildren:","true`, and a real `expand()` that calls `loadRefBody` — so the content fetch","is deferred until the render pipeline walks into that subtree. It also","dedupes colliding trails with `-2`/`-3` suffixes."]},"column":{"s":0,"e":49}},"dim":["","paragraph.219"],"code":"**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes."},{"id":"/root/children/219/children/0","type":"strong","loc":{"start":41154,"end":41210,"line":{"s":1127,"e":1127,"code":["**`insertRefsAsSubtree` is the structural odd one out.** Every other verb"]},"column":{"s":0,"e":56}},"dim":["","paragraph.219","strong.0"],"code":"**`insertRefsAsSubtree` is the structural odd one out.**"},{"id":"/root/children/219/children/0/children/0","type":"inlineCode","loc":{"start":41156,"end":41177,"line":{"s":1127,"e":1127,"code":["**`insertRefsAsSubtree` is the structural odd one out.** Every other verb"]},"column":{"s":2,"e":23}},"dim":["","paragraph.219","strong.0","inlineCode.0"],"code":"`insertRefsAsSubtree`"},{"id":"/root/children/219/children/0/children/1","type":"text","loc":{"start":41177,"end":41208,"line":{"s":1127,"e":1127,"code":["**`insertRefsAsSubtree` is the structural odd one out.** Every other verb"]},"column":{"s":23,"e":54}},"dim":["","paragraph.219","strong.0","text.1"],"code":" is the structural odd one out."},{"id":"/root/children/219/children/1","type":"text","loc":{"start":41210,"end":41262,"line":{"s":1127,"e":1128,"code":["**`insertRefsAsSubtree` is the structural odd one out.** Every other verb","yields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)"]},"column":{"s":56,"e":34}},"dim":["","paragraph.219","text.1"],"code":" Every other verb\nyields exactly one leaf Fragment ("},{"id":"/root/children/219/children/2","type":"inlineCode","loc":{"start":41262,"end":41282,"line":{"s":1128,"e":1128,"code":["yields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)"]},"column":{"s":34,"e":54}},"dim":["","paragraph.219","inlineCode.2"],"code":"`hasChildren: false`"},{"id":"/root/children/219/children/3","type":"text","loc":{"start":41282,"end":41290,"line":{"s":1128,"e":1128,"code":["yields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)"]},"column":{"s":54,"e":62}},"dim":["","paragraph.219","text.3"],"code":", inert "},{"id":"/root/children/219/children/4","type":"inlineCode","loc":{"start":41290,"end":41300,"line":{"s":1128,"e":1128,"code":["yields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)"]},"column":{"s":62,"e":72}},"dim":["","paragraph.219","inlineCode.4"],"code":"`expand()`"},{"id":"/root/children/219/children/5","type":"text","loc":{"start":41300,"end":41346,"line":{"s":1128,"e":1129,"code":["yields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)","whose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out"]},"column":{"s":72,"e":44}},"dim":["","paragraph.219","text.5"],"code":")\nwhose heading is an invisible HTML comment. "},{"id":"/root/children/219/children/6","type":"inlineCode","loc":{"start":41346,"end":41367,"line":{"s":1129,"e":1129,"code":["whose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out"]},"column":{"s":44,"e":65}},"dim":["","paragraph.219","inlineCode.6"],"code":"`insertRefsAsSubtree`"},{"id":"/root/children/219/children/7","type":"text","loc":{"start":41367,"end":41393,"line":{"s":1129,"e":1130,"code":["whose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out","to one Fragment _per item_, each with a real visible heading, `hasChildren:"]},"column":{"s":65,"e":16}},"dim":["","paragraph.219","text.7"],"code":" fans out\nto one Fragment "},{"id":"/root/children/219/children/8","type":"emphasis","loc":{"start":41393,"end":41403,"line":{"s":1130,"e":1130,"code":["to one Fragment _per item_, each with a real visible heading, `hasChildren:"]},"column":{"s":16,"e":26}},"dim":["","paragraph.219","emphasis.8"],"code":"_per item_"},{"id":"/root/children/219/children/8/children/0","type":"text","loc":{"start":41394,"end":41402,"line":{"s":1130,"e":1130,"code":["to one Fragment _per item_, each with a real visible heading, `hasChildren:"]},"column":{"s":17,"e":25}},"dim":["","paragraph.219","emphasis.8","text.0"],"code":"per item"},{"id":"/root/children/219/children/9","type":"text","loc":{"start":41403,"end":41439,"line":{"s":1130,"e":1130,"code":["to one Fragment _per item_, each with a real visible heading, `hasChildren:"]},"column":{"s":26,"e":62}},"dim":["","paragraph.219","text.9"],"code":", each with a real visible heading, "},{"id":"/root/children/219/children/10","type":"inlineCode","loc":{"start":41439,"end":41458,"line":{"s":1130,"e":1131,"code":["to one Fragment _per item_, each with a real visible heading, `hasChildren:","true`, and a real `expand()` that calls `loadRefBody` — so the content fetch"]},"column":{"s":62,"e":5}},"dim":["","paragraph.219","inlineCode.10"],"code":"`hasChildren:\ntrue`"},{"id":"/root/children/219/children/11","type":"text","loc":{"start":41458,"end":41471,"line":{"s":1131,"e":1131,"code":["true`, and a real `expand()` that calls `loadRefBody` — so the content fetch"]},"column":{"s":5,"e":18}},"dim":["","paragraph.219","text.11"],"code":", and a real "},{"id":"/root/children/219/children/12","type":"inlineCode","loc":{"start":41471,"end":41481,"line":{"s":1131,"e":1131,"code":["true`, and a real `expand()` that calls `loadRefBody` — so the content fetch"]},"column":{"s":18,"e":28}},"dim":["","paragraph.219","inlineCode.12"],"code":"`expand()`"},{"id":"/root/children/219/children/13","type":"text","loc":{"start":41481,"end":41493,"line":{"s":1131,"e":1131,"code":["true`, and a real `expand()` that calls `loadRefBody` — so the content fetch"]},"column":{"s":28,"e":40}},"dim":["","paragraph.219","text.13"],"code":" that calls "},{"id":"/root/children/219/children/14","type":"inlineCode","loc":{"start":41493,"end":41506,"line":{"s":1131,"e":1131,"code":["true`, and a real `expand()` that calls `loadRefBody` — so the content fetch"]},"column":{"s":40,"e":53}},"dim":["","paragraph.219","inlineCode.14"],"code":"`loadRefBody`"},{"id":"/root/children/219/children/15","type":"text","loc":{"start":41506,"end":41631,"line":{"s":1131,"e":1133,"code":["true`, and a real `expand()` that calls `loadRefBody` — so the content fetch","is deferred until the render pipeline walks into that subtree. It also","dedupes colliding trails with `-2`/`-3` suffixes."]},"column":{"s":53,"e":30}},"dim":["","paragraph.219","text.15"],"code":" — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with "},{"id":"/root/children/219/children/16","type":"inlineCode","loc":{"start":41631,"end":41635,"line":{"s":1133,"e":1133,"code":["dedupes colliding trails with `-2`/`-3` suffixes."]},"column":{"s":30,"e":34}},"dim":["","paragraph.219","inlineCode.16"],"code":"`-2`"},{"id":"/root/children/219/children/17","type":"text","loc":{"start":41635,"end":41636,"line":{"s":1133,"e":1133,"code":["dedupes colliding trails with `-2`/`-3` suffixes."]},"column":{"s":34,"e":35}},"dim":["","paragraph.219","text.17"],"code":"/"},{"id":"/root/children/219/children/18","type":"inlineCode","loc":{"start":41636,"end":41640,"line":{"s":1133,"e":1133,"code":["dedupes colliding trails with `-2`/`-3` suffixes."]},"column":{"s":35,"e":39}},"dim":["","paragraph.219","inlineCode.18"],"code":"`-3`"},{"id":"/root/children/219/children/19","type":"text","loc":{"start":41640,"end":41650,"line":{"s":1133,"e":1133,"code":["dedupes colliding trails with `-2`/`-3` suffixes."]},"column":{"s":39,"e":49}},"dim":["","paragraph.219","text.19"],"code":" suffixes."},{"id":"/root/children/220","type":"paragraph","loc":{"start":41652,"end":41888,"line":{"s":1135,"e":1138,"code":["**`source` tagging** (conversion-tree provenance) rides on `insert`,","`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never","carries it; `insertRefsAsSubtree` derives `sourceFragment` itself from","`buildRefId(item)`."]},"column":{"s":0,"e":19}},"dim":["","paragraph.220"],"code":"**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`."},{"id":"/root/children/220/children/0","type":"strong","loc":{"start":41652,"end":41672,"line":{"s":1135,"e":1135,"code":["**`source` tagging** (conversion-tree provenance) rides on `insert`,"]},"column":{"s":0,"e":20}},"dim":["","paragraph.220","strong.0"],"code":"**`source` tagging**"},{"id":"/root/children/220/children/0/children/0","type":"inlineCode","loc":{"start":41654,"end":41662,"line":{"s":1135,"e":1135,"code":["**`source` tagging** (conversion-tree provenance) rides on `insert`,"]},"column":{"s":2,"e":10}},"dim":["","paragraph.220","strong.0","inlineCode.0"],"code":"`source`"},{"id":"/root/children/220/children/0/children/1","type":"text","loc":{"start":41662,"end":41670,"line":{"s":1135,"e":1135,"code":["**`source` tagging** (conversion-tree provenance) rides on `insert`,"]},"column":{"s":10,"e":18}},"dim":["","paragraph.220","strong.0","text.1"],"code":" tagging"},{"id":"/root/children/220/children/1","type":"text","loc":{"start":41672,"end":41711,"line":{"s":1135,"e":1135,"code":["**`source` tagging** (conversion-tree provenance) rides on `insert`,"]},"column":{"s":20,"e":59}},"dim":["","paragraph.220","text.1"],"code":" (conversion-tree provenance) rides on "},{"id":"/root/children/220/children/2","type":"inlineCode","loc":{"start":41711,"end":41719,"line":{"s":1135,"e":1135,"code":["**`source` tagging** (conversion-tree provenance) rides on `insert`,"]},"column":{"s":59,"e":67}},"dim":["","paragraph.220","inlineCode.2"],"code":"`insert`"},{"id":"/root/children/220/children/3","type":"text","loc":{"start":41719,"end":41721,"line":{"s":1135,"e":1136,"code":["**`source` tagging** (conversion-tree provenance) rides on `insert`,","`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never"]},"column":{"s":67,"e":0}},"dim":["","paragraph.220","text.3"],"code":",\n"},{"id":"/root/children/220/children/4","type":"inlineCode","loc":{"start":41721,"end":41735,"line":{"s":1136,"e":1136,"code":["`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never"]},"column":{"s":0,"e":14}},"dim":["","paragraph.220","inlineCode.4"],"code":"`insertNljson`"},{"id":"/root/children/220/children/5","type":"text","loc":{"start":41735,"end":41737,"line":{"s":1136,"e":1136,"code":["`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never"]},"column":{"s":14,"e":16}},"dim":["","paragraph.220","text.5"],"code":", "},{"id":"/root/children/220/children/6","type":"inlineCode","loc":{"start":41737,"end":41755,"line":{"s":1136,"e":1136,"code":["`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never"]},"column":{"s":16,"e":34}},"dim":["","paragraph.220","inlineCode.6"],"code":"`insertRefsAsList`"},{"id":"/root/children/220/children/7","type":"text","loc":{"start":41755,"end":41761,"line":{"s":1136,"e":1136,"code":["`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never"]},"column":{"s":34,"e":40}},"dim":["","paragraph.220","text.7"],"code":", and "},{"id":"/root/children/220/children/8","type":"inlineCode","loc":{"start":41761,"end":41781,"line":{"s":1136,"e":1136,"code":["`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never"]},"column":{"s":40,"e":60}},"dim":["","paragraph.220","inlineCode.8"],"code":"`insertRefsAsNljson`"},{"id":"/root/children/220/children/9","type":"text","loc":{"start":41781,"end":41783,"line":{"s":1136,"e":1136,"code":["`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never"]},"column":{"s":60,"e":62}},"dim":["","paragraph.220","text.9"],"code":". "},{"id":"/root/children/220/children/10","type":"inlineCode","loc":{"start":41783,"end":41791,"line":{"s":1136,"e":1136,"code":["`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never"]},"column":{"s":62,"e":70}},"dim":["","paragraph.220","inlineCode.10"],"code":"`inject`"},{"id":"/root/children/220/children/11","type":"text","loc":{"start":41791,"end":41810,"line":{"s":1136,"e":1137,"code":["`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never","carries it; `insertRefsAsSubtree` derives `sourceFragment` itself from"]},"column":{"s":70,"e":12}},"dim":["","paragraph.220","text.11"],"code":" never\ncarries it; "},{"id":"/root/children/220/children/12","type":"inlineCode","loc":{"start":41810,"end":41831,"line":{"s":1137,"e":1137,"code":["carries it; `insertRefsAsSubtree` derives `sourceFragment` itself from"]},"column":{"s":12,"e":33}},"dim":["","paragraph.220","inlineCode.12"],"code":"`insertRefsAsSubtree`"},{"id":"/root/children/220/children/13","type":"text","loc":{"start":41831,"end":41840,"line":{"s":1137,"e":1137,"code":["carries it; `insertRefsAsSubtree` derives `sourceFragment` itself from"]},"column":{"s":33,"e":42}},"dim":["","paragraph.220","text.13"],"code":" derives "},{"id":"/root/children/220/children/14","type":"inlineCode","loc":{"start":41840,"end":41856,"line":{"s":1137,"e":1137,"code":["carries it; `insertRefsAsSubtree` derives `sourceFragment` itself from"]},"column":{"s":42,"e":58}},"dim":["","paragraph.220","inlineCode.14"],"code":"`sourceFragment`"},{"id":"/root/children/220/children/15","type":"text","loc":{"start":41856,"end":41869,"line":{"s":1137,"e":1138,"code":["carries it; `insertRefsAsSubtree` derives `sourceFragment` itself from","`buildRefId(item)`."]},"column":{"s":58,"e":0}},"dim":["","paragraph.220","text.15"],"code":" itself from\n"},{"id":"/root/children/220/children/16","type":"inlineCode","loc":{"start":41869,"end":41887,"line":{"s":1138,"e":1138,"code":["`buildRefId(item)`."]},"column":{"s":0,"e":18}},"dim":["","paragraph.220","inlineCode.16"],"code":"`buildRefId(item)`"},{"id":"/root/children/220/children/17","type":"text","loc":{"start":41887,"end":41888,"line":{"s":1138,"e":1138,"code":["`buildRefId(item)`."]},"column":{"s":18,"e":19}},"dim":["","paragraph.220","text.17"],"code":"."},{"id":"/root/children/221","type":"paragraph","loc":{"start":41890,"end":42261,"line":{"s":1140,"e":1144,"code":["**Two dispatch sites** handle these: `processExtructionResult` yields real","Fragments, while the array walker in `resolveChildTree` stringifies commands","into a parent's `children` text. `insertRefsAsSubtree` is deliberately absent","from the second — nested inside a `children` resolution there is no lazy","expansion in a flat string context, so it contributes nothing there."]},"column":{"s":0,"e":68}},"dim":["","paragraph.221"],"code":"**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there."},{"id":"/root/children/221/children/0","type":"strong","loc":{"start":41890,"end":41912,"line":{"s":1140,"e":1140,"code":["**Two dispatch sites** handle these: `processExtructionResult` yields real"]},"column":{"s":0,"e":22}},"dim":["","paragraph.221","strong.0"],"code":"**Two dispatch sites**"},{"id":"/root/children/221/children/0/children/0","type":"text","loc":{"start":41892,"end":41910,"line":{"s":1140,"e":1140,"code":["**Two dispatch sites** handle these: `processExtructionResult` yields real"]},"column":{"s":2,"e":20}},"dim":["","paragraph.221","strong.0","text.0"],"code":"Two dispatch sites"},{"id":"/root/children/221/children/1","type":"text","loc":{"start":41912,"end":41927,"line":{"s":1140,"e":1140,"code":["**Two dispatch sites** handle these: `processExtructionResult` yields real"]},"column":{"s":22,"e":37}},"dim":["","paragraph.221","text.1"],"code":" handle these: "},{"id":"/root/children/221/children/2","type":"inlineCode","loc":{"start":41927,"end":41952,"line":{"s":1140,"e":1140,"code":["**Two dispatch sites** handle these: `processExtructionResult` yields real"]},"column":{"s":37,"e":62}},"dim":["","paragraph.221","inlineCode.2"],"code":"`processExtructionResult`"},{"id":"/root/children/221/children/3","type":"text","loc":{"start":41952,"end":42002,"line":{"s":1140,"e":1141,"code":["**Two dispatch sites** handle these: `processExtructionResult` yields real","Fragments, while the array walker in `resolveChildTree` stringifies commands"]},"column":{"s":62,"e":37}},"dim":["","paragraph.221","text.3"],"code":" yields real\nFragments, while the array walker in "},{"id":"/root/children/221/children/4","type":"inlineCode","loc":{"start":42002,"end":42020,"line":{"s":1141,"e":1141,"code":["Fragments, while the array walker in `resolveChildTree` stringifies commands"]},"column":{"s":37,"e":55}},"dim":["","paragraph.221","inlineCode.4"],"code":"`resolveChildTree`"},{"id":"/root/children/221/children/5","type":"text","loc":{"start":42020,"end":42058,"line":{"s":1141,"e":1142,"code":["Fragments, while the array walker in `resolveChildTree` stringifies commands","into a parent's `children` text. `insertRefsAsSubtree` is deliberately absent"]},"column":{"s":55,"e":16}},"dim":["","paragraph.221","text.5"],"code":" stringifies commands\ninto a parent's "},{"id":"/root/children/221/children/6","type":"inlineCode","loc":{"start":42058,"end":42068,"line":{"s":1142,"e":1142,"code":["into a parent's `children` text. `insertRefsAsSubtree` is deliberately absent"]},"column":{"s":16,"e":26}},"dim":["","paragraph.221","inlineCode.6"],"code":"`children`"},{"id":"/root/children/221/children/7","type":"text","loc":{"start":42068,"end":42075,"line":{"s":1142,"e":1142,"code":["into a parent's `children` text. `insertRefsAsSubtree` is deliberately absent"]},"column":{"s":26,"e":33}},"dim":["","paragraph.221","text.7"],"code":" text. "},{"id":"/root/children/221/children/8","type":"inlineCode","loc":{"start":42075,"end":42096,"line":{"s":1142,"e":1142,"code":["into a parent's `children` text. `insertRefsAsSubtree` is deliberately absent"]},"column":{"s":33,"e":54}},"dim":["","paragraph.221","inlineCode.8"],"code":"`insertRefsAsSubtree`"},{"id":"/root/children/221/children/9","type":"text","loc":{"start":42096,"end":42154,"line":{"s":1142,"e":1143,"code":["into a parent's `children` text. `insertRefsAsSubtree` is deliberately absent","from the second — nested inside a `children` resolution there is no lazy"]},"column":{"s":54,"e":34}},"dim":["","paragraph.221","text.9"],"code":" is deliberately absent\nfrom the second — nested inside a "},{"id":"/root/children/221/children/10","type":"inlineCode","loc":{"start":42154,"end":42164,"line":{"s":1143,"e":1143,"code":["from the second — nested inside a `children` resolution there is no lazy"]},"column":{"s":34,"e":44}},"dim":["","paragraph.221","inlineCode.10"],"code":"`children`"},{"id":"/root/children/221/children/11","type":"text","loc":{"start":42164,"end":42261,"line":{"s":1143,"e":1144,"code":["from the second — nested inside a `children` resolution there is no lazy","expansion in a flat string context, so it contributes nothing there."]},"column":{"s":44,"e":68}},"dim":["","paragraph.221","text.11"],"code":" resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there."},{"id":"/root/children/222","type":"paragraph","loc":{"start":42263,"end":42547,"line":{"s":1146,"e":1149,"code":["Under the hood every helper produces a command object","(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.","The extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.","A bare non-array object yields nothing — only `undefined` or an array is valid."]},"column":{"s":0,"e":79}},"dim":["","paragraph.222"],"code":"Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid."},{"id":"/root/children/222/children/0","type":"text","loc":{"start":42263,"end":42318,"line":{"s":1146,"e":1147,"code":["Under the hood every helper produces a command object","(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes."]},"column":{"s":0,"e":1}},"dim":["","paragraph.222","text.0"],"code":"Under the hood every helper produces a command object\n("},{"id":"/root/children/222/children/1","type":"inlineCode","loc":{"start":42318,"end":42337,"line":{"s":1147,"e":1147,"code":["(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes."]},"column":{"s":1,"e":20}},"dim":["","paragraph.222","inlineCode.1"],"code":"`{ insert: [...] }`"},{"id":"/root/children/222/children/2","type":"text","loc":{"start":42337,"end":42340,"line":{"s":1147,"e":1147,"code":["(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes."]},"column":{"s":20,"e":23}},"dim":["","paragraph.222","text.2"],"code":" / "},{"id":"/root/children/222/children/3","type":"inlineCode","loc":{"start":42340,"end":42359,"line":{"s":1147,"e":1147,"code":["(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes."]},"column":{"s":23,"e":42}},"dim":["","paragraph.222","inlineCode.3"],"code":"`{ inject: \"...\" }`"},{"id":"/root/children/222/children/4","type":"text","loc":{"start":42359,"end":42428,"line":{"s":1147,"e":1148,"code":["(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.","The extruction must return an array `[cmd1, cmd2, ...]` to yield fragments."]},"column":{"s":42,"e":36}},"dim":["","paragraph.222","text.4"],"code":" / …) that the runner processes.\nThe extruction must return an array "},{"id":"/root/children/222/children/5","type":"inlineCode","loc":{"start":42428,"end":42447,"line":{"s":1148,"e":1148,"code":["The extruction must return an array `[cmd1, cmd2, ...]` to yield fragments."]},"column":{"s":36,"e":55}},"dim":["","paragraph.222","inlineCode.5"],"code":"`[cmd1, cmd2, ...]`"},{"id":"/root/children/222/children/6","type":"text","loc":{"start":42447,"end":42514,"line":{"s":1148,"e":1149,"code":["The extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.","A bare non-array object yields nothing — only `undefined` or an array is valid."]},"column":{"s":55,"e":46}},"dim":["","paragraph.222","text.6"],"code":" to yield fragments.\nA bare non-array object yields nothing — only "},{"id":"/root/children/222/children/7","type":"inlineCode","loc":{"start":42514,"end":42525,"line":{"s":1149,"e":1149,"code":["A bare non-array object yields nothing — only `undefined` or an array is valid."]},"column":{"s":46,"e":57}},"dim":["","paragraph.222","inlineCode.7"],"code":"`undefined`"},{"id":"/root/children/222/children/8","type":"text","loc":{"start":42525,"end":42547,"line":{"s":1149,"e":1149,"code":["A bare non-array object yields nothing — only `undefined` or an array is valid."]},"column":{"s":57,"e":79}},"dim":["","paragraph.222","text.8"],"code":" or an array is valid."},{"id":"/root/children/223","type":"paragraph","loc":{"start":42549,"end":42584,"line":{"s":1151,"e":1151,"code":["**Example — injecting a preamble:**"]},"column":{"s":0,"e":35}},"dim":["","paragraph.223"],"code":"**Example — injecting a preamble:**"},{"id":"/root/children/223/children/0","type":"strong","loc":{"start":42549,"end":42584,"line":{"s":1151,"e":1151,"code":["**Example — injecting a preamble:**"]},"column":{"s":0,"e":35}},"dim":["","paragraph.223","strong.0"],"code":"**Example — injecting a preamble:**"},{"id":"/root/children/223/children/0/children/0","type":"text","loc":{"start":42551,"end":42582,"line":{"s":1151,"e":1151,"code":["**Example — injecting a preamble:**"]},"column":{"s":2,"e":33}},"dim":["","paragraph.223","strong.0","text.0"],"code":"Example — injecting a preamble:"},{"id":"/root/children/224","type":"code","loc":{"start":42587,"end":42704,"line":{"s":1154,"e":1160,"code":["```","## ${notice}","","\\`\\`\\`javascript","return inject(\"> **Note:** this document is generated from live data.\")","\\`\\`\\`","```"]},"column":{"s":0,"e":3}},"dim":["","code.224"],"code":"```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```","symbName":"code","symbRange":[42706,45728],"symbRangeL":[null,1235],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n"},{"id":"/root/children/225","type":"paragraph","loc":{"start":42706,"end":42872,"line":{"s":1162,"e":1164,"code":["This produces a Fragment whose `toString()` is just the blockquote — no","heading comment wrapping it. The consumer sees clean markdown without","synthetic HTML comments."]},"column":{"s":0,"e":24}},"dim":["","paragraph.225"],"code":"This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments."},{"id":"/root/children/225/children/0","type":"text","loc":{"start":42706,"end":42737,"line":{"s":1162,"e":1162,"code":["This produces a Fragment whose `toString()` is just the blockquote — no"]},"column":{"s":0,"e":31}},"dim":["","paragraph.225","text.0"],"code":"This produces a Fragment whose "},{"id":"/root/children/225/children/1","type":"inlineCode","loc":{"start":42737,"end":42749,"line":{"s":1162,"e":1162,"code":["This produces a Fragment whose `toString()` is just the blockquote — no"]},"column":{"s":31,"e":43}},"dim":["","paragraph.225","inlineCode.1"],"code":"`toString()`"},{"id":"/root/children/225/children/2","type":"text","loc":{"start":42749,"end":42872,"line":{"s":1162,"e":1164,"code":["This produces a Fragment whose `toString()` is just the blockquote — no","heading comment wrapping it. The consumer sees clean markdown without","synthetic HTML comments."]},"column":{"s":43,"e":24}},"dim":["","paragraph.225","text.2"],"code":" is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments."},{"id":"/root/children/226","type":"paragraph","loc":{"start":42874,"end":42899,"line":{"s":1166,"e":1166,"code":["**Implementation notes:**"]},"column":{"s":0,"e":25}},"dim":["","paragraph.226"],"code":"**Implementation notes:**"},{"id":"/root/children/226/children/0","type":"strong","loc":{"start":42874,"end":42899,"line":{"s":1166,"e":1166,"code":["**Implementation notes:**"]},"column":{"s":0,"e":25}},"dim":["","paragraph.226","strong.0"],"code":"**Implementation notes:**"},{"id":"/root/children/226/children/0/children/0","type":"text","loc":{"start":42876,"end":42897,"line":{"s":1166,"e":1166,"code":["**Implementation notes:**"]},"column":{"s":2,"e":23}},"dim":["","paragraph.226","strong.0","text.0"],"code":"Implementation notes:"},{"id":"/root/children/227","type":"list","loc":{"start":42901,"end":43683,"line":{"s":1168,"e":1179,"code":["- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with","  `body = normalizeFragmentBody(injectValue)` — same serialization as","  `buildInsertFragment` (array→joined, object→JSON, primitive→String).","- `normalizeFragmentBody()` is the shared helper used by both protocols,","  extracted during the inject implementation.","- `processExtructionResult()` (the async generator in `mdt.js`) iterates","  each command in the array and yields a Fragment per command — `insert`","  and `inject` can be mixed in any order.","- Non-array results are silently ignored (yield nothing). Only `undefined`","  (skip) or `[cmd, ...]` (yield) are valid return values.","- `inject` fragments have `hasChildren: false` and `expand()` returns an","  empty async generator — they are always leaf nodes."]},"column":{"s":0,"e":53}},"dim":["","list.227"],"code":"- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.","symbName":"list","symbRange":[43685,44075],"symbRangeL":[1168,1190],"outerCode":"  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior","outerHtml":"<p>  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</p><ul><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>"},{"id":"/root/children/227/children/0","type":"listItem","loc":{"start":42901,"end":43116,"line":{"s":1168,"e":1170,"code":["- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with","  `body = normalizeFragmentBody(injectValue)` — same serialization as","  `buildInsertFragment` (array→joined, object→JSON, primitive→String)."]},"column":{"s":0,"e":70}},"dim":["","list.227","listItem.0"],"code":"- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String)."},{"id":"/root/children/227/children/0/children/0","type":"paragraph","loc":{"start":42903,"end":43116,"line":{"s":1168,"e":1170,"code":["- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with","  `body = normalizeFragmentBody(injectValue)` — same serialization as","  `buildInsertFragment` (array→joined, object→JSON, primitive→String)."]},"column":{"s":2,"e":70}},"dim":["","list.227","listItem.0","paragraph.0"],"code":"`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String)."},{"id":"/root/children/227/children/0/children/0/children/0","type":"inlineCode","loc":{"start":42903,"end":42937,"line":{"s":1168,"e":1168,"code":["- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with"]},"column":{"s":2,"e":36}},"dim":["","list.227","listItem.0","paragraph.0","inlineCode.0"],"code":"`buildInjectFragment(injectValue)`"},{"id":"/root/children/227/children/0/children/0/children/1","type":"text","loc":{"start":42937,"end":42941,"line":{"s":1168,"e":1168,"code":["- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with"]},"column":{"s":36,"e":40}},"dim":["","list.227","listItem.0","paragraph.0","text.1"],"code":" in "},{"id":"/root/children/227/children/0/children/0/children/2","type":"inlineCode","loc":{"start":42941,"end":42949,"line":{"s":1168,"e":1168,"code":["- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with"]},"column":{"s":40,"e":48}},"dim":["","list.227","listItem.0","paragraph.0","inlineCode.2"],"code":"`mdt.js`"},{"id":"/root/children/227/children/0/children/0/children/3","type":"text","loc":{"start":42949,"end":42976,"line":{"s":1168,"e":1169,"code":["- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with","  `body = normalizeFragmentBody(injectValue)` — same serialization as"]},"column":{"s":48,"e":0}},"dim":["","list.227","listItem.0","paragraph.0","text.3"],"code":" creates the Fragment with\n"},{"id":"/root/children/227/children/0/children/0/children/4","type":"inlineCode","loc":{"start":42978,"end":43021,"line":{"s":1169,"e":1169,"code":["  `body = normalizeFragmentBody(injectValue)` — same serialization as"]},"column":{"s":2,"e":45}},"dim":["","list.227","listItem.0","paragraph.0","inlineCode.4"],"code":"`body = normalizeFragmentBody(injectValue)`"},{"id":"/root/children/227/children/0/children/0/children/5","type":"text","loc":{"start":43021,"end":43046,"line":{"s":1169,"e":1170,"code":["  `body = normalizeFragmentBody(injectValue)` — same serialization as","  `buildInsertFragment` (array→joined, object→JSON, primitive→String)."]},"column":{"s":45,"e":0}},"dim":["","list.227","listItem.0","paragraph.0","text.5"],"code":" — same serialization as\n"},{"id":"/root/children/227/children/0/children/0/children/6","type":"inlineCode","loc":{"start":43048,"end":43069,"line":{"s":1170,"e":1170,"code":["  `buildInsertFragment` (array→joined, object→JSON, primitive→String)."]},"column":{"s":2,"e":23}},"dim":["","list.227","listItem.0","paragraph.0","inlineCode.6"],"code":"`buildInsertFragment`"},{"id":"/root/children/227/children/0/children/0/children/7","type":"text","loc":{"start":43069,"end":43116,"line":{"s":1170,"e":1170,"code":["  `buildInsertFragment` (array→joined, object→JSON, primitive→String)."]},"column":{"s":23,"e":70}},"dim":["","list.227","listItem.0","paragraph.0","text.7"],"code":" (array→joined, object→JSON, primitive→String)."},{"id":"/root/children/227/children/1","type":"listItem","loc":{"start":43117,"end":43235,"line":{"s":1171,"e":1172,"code":["- `normalizeFragmentBody()` is the shared helper used by both protocols,","  extracted during the inject implementation."]},"column":{"s":0,"e":45}},"dim":["","list.227","listItem.1"],"code":"- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation."},{"id":"/root/children/227/children/1/children/0","type":"paragraph","loc":{"start":43119,"end":43235,"line":{"s":1171,"e":1172,"code":["- `normalizeFragmentBody()` is the shared helper used by both protocols,","  extracted during the inject implementation."]},"column":{"s":2,"e":45}},"dim":["","list.227","listItem.1","paragraph.0"],"code":"`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation."},{"id":"/root/children/227/children/1/children/0/children/0","type":"inlineCode","loc":{"start":43119,"end":43144,"line":{"s":1171,"e":1171,"code":["- `normalizeFragmentBody()` is the shared helper used by both protocols,"]},"column":{"s":2,"e":27}},"dim":["","list.227","listItem.1","paragraph.0","inlineCode.0"],"code":"`normalizeFragmentBody()`"},{"id":"/root/children/227/children/1/children/0/children/1","type":"text","loc":{"start":43144,"end":43235,"line":{"s":1171,"e":1172,"code":["- `normalizeFragmentBody()` is the shared helper used by both protocols,","  extracted during the inject implementation."]},"column":{"s":27,"e":45}},"dim":["","list.227","listItem.1","paragraph.0","text.1"],"code":" is the shared helper used by both protocols,\n  extracted during the inject implementation."},{"id":"/root/children/227/children/2","type":"listItem","loc":{"start":43236,"end":43423,"line":{"s":1173,"e":1175,"code":["- `processExtructionResult()` (the async generator in `mdt.js`) iterates","  each command in the array and yields a Fragment per command — `insert`","  and `inject` can be mixed in any order."]},"column":{"s":0,"e":41}},"dim":["","list.227","listItem.2"],"code":"- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order."},{"id":"/root/children/227/children/2/children/0","type":"paragraph","loc":{"start":43238,"end":43423,"line":{"s":1173,"e":1175,"code":["- `processExtructionResult()` (the async generator in `mdt.js`) iterates","  each command in the array and yields a Fragment per command — `insert`","  and `inject` can be mixed in any order."]},"column":{"s":2,"e":41}},"dim":["","list.227","listItem.2","paragraph.0"],"code":"`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order."},{"id":"/root/children/227/children/2/children/0/children/0","type":"inlineCode","loc":{"start":43238,"end":43265,"line":{"s":1173,"e":1173,"code":["- `processExtructionResult()` (the async generator in `mdt.js`) iterates"]},"column":{"s":2,"e":29}},"dim":["","list.227","listItem.2","paragraph.0","inlineCode.0"],"code":"`processExtructionResult()`"},{"id":"/root/children/227/children/2/children/0/children/1","type":"text","loc":{"start":43265,"end":43290,"line":{"s":1173,"e":1173,"code":["- `processExtructionResult()` (the async generator in `mdt.js`) iterates"]},"column":{"s":29,"e":54}},"dim":["","list.227","listItem.2","paragraph.0","text.1"],"code":" (the async generator in "},{"id":"/root/children/227/children/2/children/0/children/2","type":"inlineCode","loc":{"start":43290,"end":43298,"line":{"s":1173,"e":1173,"code":["- `processExtructionResult()` (the async generator in `mdt.js`) iterates"]},"column":{"s":54,"e":62}},"dim":["","list.227","listItem.2","paragraph.0","inlineCode.2"],"code":"`mdt.js`"},{"id":"/root/children/227/children/2/children/0/children/3","type":"text","loc":{"start":43298,"end":43373,"line":{"s":1173,"e":1174,"code":["- `processExtructionResult()` (the async generator in `mdt.js`) iterates","  each command in the array and yields a Fragment per command — `insert`"]},"column":{"s":62,"e":64}},"dim":["","list.227","listItem.2","paragraph.0","text.3"],"code":") iterates\n  each command in the array and yields a Fragment per command — "},{"id":"/root/children/227/children/2/children/0/children/4","type":"inlineCode","loc":{"start":43373,"end":43381,"line":{"s":1174,"e":1174,"code":["  each command in the array and yields a Fragment per command — `insert`"]},"column":{"s":64,"e":72}},"dim":["","list.227","listItem.2","paragraph.0","inlineCode.4"],"code":"`insert`"},{"id":"/root/children/227/children/2/children/0/children/5","type":"text","loc":{"start":43381,"end":43388,"line":{"s":1174,"e":1175,"code":["  each command in the array and yields a Fragment per command — `insert`","  and `inject` can be mixed in any order."]},"column":{"s":72,"e":6}},"dim":["","list.227","listItem.2","paragraph.0","text.5"],"code":"\n  and "},{"id":"/root/children/227/children/2/children/0/children/6","type":"inlineCode","loc":{"start":43388,"end":43396,"line":{"s":1175,"e":1175,"code":["  and `inject` can be mixed in any order."]},"column":{"s":6,"e":14}},"dim":["","list.227","listItem.2","paragraph.0","inlineCode.6"],"code":"`inject`"},{"id":"/root/children/227/children/2/children/0/children/7","type":"text","loc":{"start":43396,"end":43423,"line":{"s":1175,"e":1175,"code":["  and `inject` can be mixed in any order."]},"column":{"s":14,"e":41}},"dim":["","list.227","listItem.2","paragraph.0","text.7"],"code":" can be mixed in any order."},{"id":"/root/children/227/children/3","type":"listItem","loc":{"start":43424,"end":43556,"line":{"s":1176,"e":1177,"code":["- Non-array results are silently ignored (yield nothing). Only `undefined`","  (skip) or `[cmd, ...]` (yield) are valid return values."]},"column":{"s":0,"e":57}},"dim":["","list.227","listItem.3"],"code":"- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values."},{"id":"/root/children/227/children/3/children/0","type":"paragraph","loc":{"start":43426,"end":43556,"line":{"s":1176,"e":1177,"code":["- Non-array results are silently ignored (yield nothing). Only `undefined`","  (skip) or `[cmd, ...]` (yield) are valid return values."]},"column":{"s":2,"e":57}},"dim":["","list.227","listItem.3","paragraph.0"],"code":"Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values."},{"id":"/root/children/227/children/3/children/0/children/0","type":"text","loc":{"start":43426,"end":43487,"line":{"s":1176,"e":1176,"code":["- Non-array results are silently ignored (yield nothing). Only `undefined`"]},"column":{"s":2,"e":63}},"dim":["","list.227","listItem.3","paragraph.0","text.0"],"code":"Non-array results are silently ignored (yield nothing). Only "},{"id":"/root/children/227/children/3/children/0/children/1","type":"inlineCode","loc":{"start":43487,"end":43498,"line":{"s":1176,"e":1176,"code":["- Non-array results are silently ignored (yield nothing). Only `undefined`"]},"column":{"s":63,"e":74}},"dim":["","list.227","listItem.3","paragraph.0","inlineCode.1"],"code":"`undefined`"},{"id":"/root/children/227/children/3/children/0/children/2","type":"text","loc":{"start":43498,"end":43511,"line":{"s":1176,"e":1177,"code":["- Non-array results are silently ignored (yield nothing). Only `undefined`","  (skip) or `[cmd, ...]` (yield) are valid return values."]},"column":{"s":74,"e":12}},"dim":["","list.227","listItem.3","paragraph.0","text.2"],"code":"\n  (skip) or "},{"id":"/root/children/227/children/3/children/0/children/3","type":"inlineCode","loc":{"start":43511,"end":43523,"line":{"s":1177,"e":1177,"code":["  (skip) or `[cmd, ...]` (yield) are valid return values."]},"column":{"s":12,"e":24}},"dim":["","list.227","listItem.3","paragraph.0","inlineCode.3"],"code":"`[cmd, ...]`"},{"id":"/root/children/227/children/3/children/0/children/4","type":"text","loc":{"start":43523,"end":43556,"line":{"s":1177,"e":1177,"code":["  (skip) or `[cmd, ...]` (yield) are valid return values."]},"column":{"s":24,"e":57}},"dim":["","list.227","listItem.3","paragraph.0","text.4"],"code":" (yield) are valid return values."},{"id":"/root/children/227/children/4","type":"listItem","loc":{"start":43557,"end":43683,"line":{"s":1178,"e":1179,"code":["- `inject` fragments have `hasChildren: false` and `expand()` returns an","  empty async generator — they are always leaf nodes."]},"column":{"s":0,"e":53}},"dim":["","list.227","listItem.4"],"code":"- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes."},{"id":"/root/children/227/children/4/children/0","type":"paragraph","loc":{"start":43559,"end":43683,"line":{"s":1178,"e":1179,"code":["- `inject` fragments have `hasChildren: false` and `expand()` returns an","  empty async generator — they are always leaf nodes."]},"column":{"s":2,"e":53}},"dim":["","list.227","listItem.4","paragraph.0"],"code":"`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes."},{"id":"/root/children/227/children/4/children/0/children/0","type":"inlineCode","loc":{"start":43559,"end":43567,"line":{"s":1178,"e":1178,"code":["- `inject` fragments have `hasChildren: false` and `expand()` returns an"]},"column":{"s":2,"e":10}},"dim":["","list.227","listItem.4","paragraph.0","inlineCode.0"],"code":"`inject`"},{"id":"/root/children/227/children/4/children/0/children/1","type":"text","loc":{"start":43567,"end":43583,"line":{"s":1178,"e":1178,"code":["- `inject` fragments have `hasChildren: false` and `expand()` returns an"]},"column":{"s":10,"e":26}},"dim":["","list.227","listItem.4","paragraph.0","text.1"],"code":" fragments have "},{"id":"/root/children/227/children/4/children/0/children/2","type":"inlineCode","loc":{"start":43583,"end":43603,"line":{"s":1178,"e":1178,"code":["- `inject` fragments have `hasChildren: false` and `expand()` returns an"]},"column":{"s":26,"e":46}},"dim":["","list.227","listItem.4","paragraph.0","inlineCode.2"],"code":"`hasChildren: false`"},{"id":"/root/children/227/children/4/children/0/children/3","type":"text","loc":{"start":43603,"end":43608,"line":{"s":1178,"e":1178,"code":["- `inject` fragments have `hasChildren: false` and `expand()` returns an"]},"column":{"s":46,"e":51}},"dim":["","list.227","listItem.4","paragraph.0","text.3"],"code":" and "},{"id":"/root/children/227/children/4/children/0/children/4","type":"inlineCode","loc":{"start":43608,"end":43618,"line":{"s":1178,"e":1178,"code":["- `inject` fragments have `hasChildren: false` and `expand()` returns an"]},"column":{"s":51,"e":61}},"dim":["","list.227","listItem.4","paragraph.0","inlineCode.4"],"code":"`expand()`"},{"id":"/root/children/227/children/4/children/0/children/5","type":"text","loc":{"start":43618,"end":43683,"line":{"s":1178,"e":1179,"code":["- `inject` fragments have `hasChildren: false` and `expand()` returns an","  empty async generator — they are always leaf nodes."]},"column":{"s":61,"e":53}},"dim":["","list.227","listItem.4","paragraph.0","text.5"],"code":" returns an\n  empty async generator — they are always leaf nodes."},{"id":"/root/children/228","type":"heading","loc":{"start":43685,"end":43724,"line":{"s":1181,"e":1181,"code":["### hasChildren & extruction evaluation"]},"column":{"s":0,"e":39}},"dim":["","heading.228"],"code":"### hasChildren & extruction evaluation","symbName":"heading","symbRange":[43726,44055],"symbRangeL":[1181,1188],"outerCode":"\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).","outerHtml":"\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>"},{"id":"/root/children/228/children/0","type":"text","loc":{"start":43689,"end":43724,"line":{"s":1181,"e":1181,"code":["### hasChildren & extruction evaluation"]},"column":{"s":4,"e":39}},"dim":["","heading.228","text.0"],"code":"hasChildren & extruction evaluation"},{"id":"/root/children/229","type":"paragraph","loc":{"start":43726,"end":44055,"line":{"s":1183,"e":1187,"code":["When `evalFn` is active, any extruction child heading causes the parent's","`hasChildren` to be `true`, since the extruction might produce an `insert`.","This ensures `rebuildMd()`-style collectors expand to find evaluated content.","Extructions that evaluate to `undefined` yield no children (the expansion","returns empty immediately)."]},"column":{"s":0,"e":27}},"dim":["","paragraph.229"],"code":"When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately)."},{"id":"/root/children/229/children/0","type":"text","loc":{"start":43726,"end":43731,"line":{"s":1183,"e":1183,"code":["When `evalFn` is active, any extruction child heading causes the parent's"]},"column":{"s":0,"e":5}},"dim":["","paragraph.229","text.0"],"code":"When "},{"id":"/root/children/229/children/1","type":"inlineCode","loc":{"start":43731,"end":43739,"line":{"s":1183,"e":1183,"code":["When `evalFn` is active, any extruction child heading causes the parent's"]},"column":{"s":5,"e":13}},"dim":["","paragraph.229","inlineCode.1"],"code":"`evalFn`"},{"id":"/root/children/229/children/2","type":"text","loc":{"start":43739,"end":43800,"line":{"s":1183,"e":1184,"code":["When `evalFn` is active, any extruction child heading causes the parent's","`hasChildren` to be `true`, since the extruction might produce an `insert`."]},"column":{"s":13,"e":0}},"dim":["","paragraph.229","text.2"],"code":" is active, any extruction child heading causes the parent's\n"},{"id":"/root/children/229/children/3","type":"inlineCode","loc":{"start":43800,"end":43813,"line":{"s":1184,"e":1184,"code":["`hasChildren` to be `true`, since the extruction might produce an `insert`."]},"column":{"s":0,"e":13}},"dim":["","paragraph.229","inlineCode.3"],"code":"`hasChildren`"},{"id":"/root/children/229/children/4","type":"text","loc":{"start":43813,"end":43820,"line":{"s":1184,"e":1184,"code":["`hasChildren` to be `true`, since the extruction might produce an `insert`."]},"column":{"s":13,"e":20}},"dim":["","paragraph.229","text.4"],"code":" to be "},{"id":"/root/children/229/children/5","type":"inlineCode","loc":{"start":43820,"end":43826,"line":{"s":1184,"e":1184,"code":["`hasChildren` to be `true`, since the extruction might produce an `insert`."]},"column":{"s":20,"e":26}},"dim":["","paragraph.229","inlineCode.5"],"code":"`true`"},{"id":"/root/children/229/children/6","type":"text","loc":{"start":43826,"end":43866,"line":{"s":1184,"e":1184,"code":["`hasChildren` to be `true`, since the extruction might produce an `insert`."]},"column":{"s":26,"e":66}},"dim":["","paragraph.229","text.6"],"code":", since the extruction might produce an "},{"id":"/root/children/229/children/7","type":"inlineCode","loc":{"start":43866,"end":43874,"line":{"s":1184,"e":1184,"code":["`hasChildren` to be `true`, since the extruction might produce an `insert`."]},"column":{"s":66,"e":74}},"dim":["","paragraph.229","inlineCode.7"],"code":"`insert`"},{"id":"/root/children/229/children/8","type":"text","loc":{"start":43874,"end":43889,"line":{"s":1184,"e":1185,"code":["`hasChildren` to be `true`, since the extruction might produce an `insert`.","This ensures `rebuildMd()`-style collectors expand to find evaluated content."]},"column":{"s":74,"e":13}},"dim":["","paragraph.229","text.8"],"code":".\nThis ensures "},{"id":"/root/children/229/children/9","type":"inlineCode","loc":{"start":43889,"end":43902,"line":{"s":1185,"e":1185,"code":["This ensures `rebuildMd()`-style collectors expand to find evaluated content."]},"column":{"s":13,"e":26}},"dim":["","paragraph.229","inlineCode.9"],"code":"`rebuildMd()`"},{"id":"/root/children/229/children/10","type":"text","loc":{"start":43902,"end":43983,"line":{"s":1185,"e":1186,"code":["This ensures `rebuildMd()`-style collectors expand to find evaluated content.","Extructions that evaluate to `undefined` yield no children (the expansion"]},"column":{"s":26,"e":29}},"dim":["","paragraph.229","text.10"],"code":"-style collectors expand to find evaluated content.\nExtructions that evaluate to "},{"id":"/root/children/229/children/11","type":"inlineCode","loc":{"start":43983,"end":43994,"line":{"s":1186,"e":1186,"code":["Extructions that evaluate to `undefined` yield no children (the expansion"]},"column":{"s":29,"e":40}},"dim":["","paragraph.229","inlineCode.11"],"code":"`undefined`"},{"id":"/root/children/229/children/12","type":"text","loc":{"start":43994,"end":44055,"line":{"s":1186,"e":1187,"code":["Extructions that evaluate to `undefined` yield no children (the expansion","returns empty immediately)."]},"column":{"s":40,"e":27}},"dim":["","paragraph.229","text.12"],"code":" yield no children (the expansion\nreturns empty immediately)."},{"id":"/root/children/230","type":"heading","loc":{"start":44057,"end":44075,"line":{"s":1189,"e":1189,"code":["### Error behavior"]},"column":{"s":0,"e":18}},"dim":["","heading.230"],"code":"### Error behavior","symbName":"heading","symbRange":[44077,44472],"symbRangeL":[1189,1198],"outerCode":"\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.","outerHtml":"\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>"},{"id":"/root/children/230/children/0","type":"text","loc":{"start":44061,"end":44075,"line":{"s":1189,"e":1189,"code":["### Error behavior"]},"column":{"s":4,"e":18}},"dim":["","heading.230","text.0"],"code":"Error behavior"},{"id":"/root/children/231","type":"list","loc":{"start":44077,"end":44295,"line":{"s":1191,"e":1193,"code":["- **No evalFn** — extruction bodies are inert (silently dropped).","- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.","- **evalFn provided, runtime error** — error propagates from the evaluation."]},"column":{"s":0,"e":76}},"dim":["","list.231"],"code":"- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.","symbName":"list","symbRange":[44297,44584],"symbRangeL":[1191,1202],"outerCode":"- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:","outerHtml":"<ul><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>"},{"id":"/root/children/231/children/0","type":"listItem","loc":{"start":44077,"end":44142,"line":{"s":1191,"e":1191,"code":["- **No evalFn** — extruction bodies are inert (silently dropped)."]},"column":{"s":0,"e":65}},"dim":["","list.231","listItem.0"],"code":"- **No evalFn** — extruction bodies are inert (silently dropped)."},{"id":"/root/children/231/children/0/children/0","type":"paragraph","loc":{"start":44079,"end":44142,"line":{"s":1191,"e":1191,"code":["- **No evalFn** — extruction bodies are inert (silently dropped)."]},"column":{"s":2,"e":65}},"dim":["","list.231","listItem.0","paragraph.0"],"code":"**No evalFn** — extruction bodies are inert (silently dropped)."},{"id":"/root/children/231/children/0/children/0/children/0","type":"strong","loc":{"start":44079,"end":44092,"line":{"s":1191,"e":1191,"code":["- **No evalFn** — extruction bodies are inert (silently dropped)."]},"column":{"s":2,"e":15}},"dim":["","list.231","listItem.0","paragraph.0","strong.0"],"code":"**No evalFn**"},{"id":"/root/children/231/children/0/children/0/children/0/children/0","type":"text","loc":{"start":44081,"end":44090,"line":{"s":1191,"e":1191,"code":["- **No evalFn** — extruction bodies are inert (silently dropped)."]},"column":{"s":4,"e":13}},"dim":["","list.231","listItem.0","paragraph.0","strong.0","text.0"],"code":"No evalFn"},{"id":"/root/children/231/children/0/children/0/children/1","type":"text","loc":{"start":44092,"end":44142,"line":{"s":1191,"e":1191,"code":["- **No evalFn** — extruction bodies are inert (silently dropped)."]},"column":{"s":15,"e":65}},"dim":["","list.231","listItem.0","paragraph.0","text.1"],"code":" — extruction bodies are inert (silently dropped)."},{"id":"/root/children/231/children/1","type":"listItem","loc":{"start":44143,"end":44218,"line":{"s":1192,"e":1192,"code":["- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates."]},"column":{"s":0,"e":75}},"dim":["","list.231","listItem.1"],"code":"- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates."},{"id":"/root/children/231/children/1/children/0","type":"paragraph","loc":{"start":44145,"end":44218,"line":{"s":1192,"e":1192,"code":["- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates."]},"column":{"s":2,"e":75}},"dim":["","list.231","listItem.1","paragraph.0"],"code":"**evalFn provided, body has JS syntax error** — `SyntaxError` propagates."},{"id":"/root/children/231/children/1/children/0/children/0","type":"strong","loc":{"start":44145,"end":44190,"line":{"s":1192,"e":1192,"code":["- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates."]},"column":{"s":2,"e":47}},"dim":["","list.231","listItem.1","paragraph.0","strong.0"],"code":"**evalFn provided, body has JS syntax error**"},{"id":"/root/children/231/children/1/children/0/children/0/children/0","type":"text","loc":{"start":44147,"end":44188,"line":{"s":1192,"e":1192,"code":["- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates."]},"column":{"s":4,"e":45}},"dim":["","list.231","listItem.1","paragraph.0","strong.0","text.0"],"code":"evalFn provided, body has JS syntax error"},{"id":"/root/children/231/children/1/children/0/children/1","type":"text","loc":{"start":44190,"end":44193,"line":{"s":1192,"e":1192,"code":["- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates."]},"column":{"s":47,"e":50}},"dim":["","list.231","listItem.1","paragraph.0","text.1"],"code":" — "},{"id":"/root/children/231/children/1/children/0/children/2","type":"inlineCode","loc":{"start":44193,"end":44206,"line":{"s":1192,"e":1192,"code":["- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates."]},"column":{"s":50,"e":63}},"dim":["","list.231","listItem.1","paragraph.0","inlineCode.2"],"code":"`SyntaxError`"},{"id":"/root/children/231/children/1/children/0/children/3","type":"text","loc":{"start":44206,"end":44218,"line":{"s":1192,"e":1192,"code":["- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates."]},"column":{"s":63,"e":75}},"dim":["","list.231","listItem.1","paragraph.0","text.3"],"code":" propagates."},{"id":"/root/children/231/children/2","type":"listItem","loc":{"start":44219,"end":44295,"line":{"s":1193,"e":1193,"code":["- **evalFn provided, runtime error** — error propagates from the evaluation."]},"column":{"s":0,"e":76}},"dim":["","list.231","listItem.2"],"code":"- **evalFn provided, runtime error** — error propagates from the evaluation."},{"id":"/root/children/231/children/2/children/0","type":"paragraph","loc":{"start":44221,"end":44295,"line":{"s":1193,"e":1193,"code":["- **evalFn provided, runtime error** — error propagates from the evaluation."]},"column":{"s":2,"e":76}},"dim":["","list.231","listItem.2","paragraph.0"],"code":"**evalFn provided, runtime error** — error propagates from the evaluation."},{"id":"/root/children/231/children/2/children/0/children/0","type":"strong","loc":{"start":44221,"end":44255,"line":{"s":1193,"e":1193,"code":["- **evalFn provided, runtime error** — error propagates from the evaluation."]},"column":{"s":2,"e":36}},"dim":["","list.231","listItem.2","paragraph.0","strong.0"],"code":"**evalFn provided, runtime error**"},{"id":"/root/children/231/children/2/children/0/children/0/children/0","type":"text","loc":{"start":44223,"end":44253,"line":{"s":1193,"e":1193,"code":["- **evalFn provided, runtime error** — error propagates from the evaluation."]},"column":{"s":4,"e":34}},"dim":["","list.231","listItem.2","paragraph.0","strong.0","text.0"],"code":"evalFn provided, runtime error"},{"id":"/root/children/231/children/2/children/0/children/1","type":"text","loc":{"start":44255,"end":44295,"line":{"s":1193,"e":1193,"code":["- **evalFn provided, runtime error** — error propagates from the evaluation."]},"column":{"s":36,"e":76}},"dim":["","list.231","listItem.2","paragraph.0","text.1"],"code":" — error propagates from the evaluation."},{"id":"/root/children/232","type":"paragraph","loc":{"start":44297,"end":44472,"line":{"s":1195,"e":1197,"code":["The snapshot test `\"syntax error in extruction body\"` documents the current","behavior without `evalFn` (silently dropped). When `evalFn` is added to that","test, it should throw."]},"column":{"s":0,"e":22}},"dim":["","paragraph.232"],"code":"The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw."},{"id":"/root/children/232/children/0","type":"text","loc":{"start":44297,"end":44315,"line":{"s":1195,"e":1195,"code":["The snapshot test `\"syntax error in extruction body\"` documents the current"]},"column":{"s":0,"e":18}},"dim":["","paragraph.232","text.0"],"code":"The snapshot test "},{"id":"/root/children/232/children/1","type":"inlineCode","loc":{"start":44315,"end":44350,"line":{"s":1195,"e":1195,"code":["The snapshot test `\"syntax error in extruction body\"` documents the current"]},"column":{"s":18,"e":53}},"dim":["","paragraph.232","inlineCode.1"],"code":"`\"syntax error in extruction body\"`"},{"id":"/root/children/232/children/2","type":"text","loc":{"start":44350,"end":44390,"line":{"s":1195,"e":1196,"code":["The snapshot test `\"syntax error in extruction body\"` documents the current","behavior without `evalFn` (silently dropped). When `evalFn` is added to that"]},"column":{"s":53,"e":17}},"dim":["","paragraph.232","text.2"],"code":" documents the current\nbehavior without "},{"id":"/root/children/232/children/3","type":"inlineCode","loc":{"start":44390,"end":44398,"line":{"s":1196,"e":1196,"code":["behavior without `evalFn` (silently dropped). When `evalFn` is added to that"]},"column":{"s":17,"e":25}},"dim":["","paragraph.232","inlineCode.3"],"code":"`evalFn`"},{"id":"/root/children/232/children/4","type":"text","loc":{"start":44398,"end":44424,"line":{"s":1196,"e":1196,"code":["behavior without `evalFn` (silently dropped). When `evalFn` is added to that"]},"column":{"s":25,"e":51}},"dim":["","paragraph.232","text.4"],"code":" (silently dropped). When "},{"id":"/root/children/232/children/5","type":"inlineCode","loc":{"start":44424,"end":44432,"line":{"s":1196,"e":1196,"code":["behavior without `evalFn` (silently dropped). When `evalFn` is added to that"]},"column":{"s":51,"e":59}},"dim":["","paragraph.232","inlineCode.5"],"code":"`evalFn`"},{"id":"/root/children/232/children/6","type":"text","loc":{"start":44432,"end":44472,"line":{"s":1196,"e":1197,"code":["behavior without `evalFn` (silently dropped). When `evalFn` is added to that","test, it should throw."]},"column":{"s":59,"e":22}},"dim":["","paragraph.232","text.6"],"code":" is added to that\ntest, it should throw."},{"id":"/root/children/233","type":"heading","loc":{"start":44474,"end":44511,"line":{"s":1199,"e":1199,"code":["### buildInsertFragment serialization"]},"column":{"s":0,"e":37}},"dim":["","heading.233"],"code":"### buildInsertFragment serialization","symbName":"heading","symbRange":[44513,44873],"symbRangeL":[1199,1210],"outerCode":"\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).","outerHtml":"\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>"},{"id":"/root/children/233/children/0","type":"text","loc":{"start":44478,"end":44511,"line":{"s":1199,"e":1199,"code":["### buildInsertFragment serialization"]},"column":{"s":4,"e":37}},"dim":["","heading.233","text.0"],"code":"buildInsertFragment serialization"},{"id":"/root/children/234","type":"paragraph","loc":{"start":44513,"end":44584,"line":{"s":1201,"e":1201,"code":["`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:"]},"column":{"s":0,"e":71}},"dim":["","paragraph.234"],"code":"`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:"},{"id":"/root/children/234/children/0","type":"inlineCode","loc":{"start":44513,"end":44552,"line":{"s":1201,"e":1201,"code":["`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:"]},"column":{"s":0,"e":39}},"dim":["","paragraph.234","inlineCode.0"],"code":"`buildInsertFragment(insertValue, ...)`"},{"id":"/root/children/234/children/1","type":"text","loc":{"start":44552,"end":44565,"line":{"s":1201,"e":1201,"code":["`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:"]},"column":{"s":39,"e":52}},"dim":["","paragraph.234","text.1"],"code":" handles the "},{"id":"/root/children/234/children/2","type":"inlineCode","loc":{"start":44565,"end":44577,"line":{"s":1201,"e":1201,"code":["`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:"]},"column":{"s":52,"e":64}},"dim":["","paragraph.234","inlineCode.2"],"code":"`{ insert }`"},{"id":"/root/children/234/children/3","type":"text","loc":{"start":44577,"end":44584,"line":{"s":1201,"e":1201,"code":["`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:"]},"column":{"s":64,"e":71}},"dim":["","paragraph.234","text.3"],"code":" value:"},{"id":"/root/children/235","type":"list","loc":{"start":44586,"end":44762,"line":{"s":1203,"e":1206,"code":["- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),","  joined with `\"\\n\"`","- **Object (non-array)** — `JSON.stringify`","- **Primitive** — `String()`"]},"column":{"s":0,"e":28}},"dim":["","list.235"],"code":"- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`","symbName":"list","symbRange":[44764,44964],"symbRangeL":[1203,1214],"outerCode":"  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:","outerHtml":"<p>  joined with `\"\\n\"`</p><ul><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>"},{"id":"/root/children/235/children/0","type":"listItem","loc":{"start":44586,"end":44689,"line":{"s":1203,"e":1204,"code":["- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),","  joined with `\"\\n\"`"]},"column":{"s":0,"e":20}},"dim":["","list.235","listItem.0"],"code":"- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`"},{"id":"/root/children/235/children/0/children/0","type":"paragraph","loc":{"start":44588,"end":44689,"line":{"s":1203,"e":1204,"code":["- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),","  joined with `\"\\n\"`"]},"column":{"s":2,"e":20}},"dim":["","list.235","listItem.0","paragraph.0"],"code":"**Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`"},{"id":"/root/children/235/children/0/children/0/children/0","type":"strong","loc":{"start":44588,"end":44597,"line":{"s":1203,"e":1203,"code":["- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),"]},"column":{"s":2,"e":11}},"dim":["","list.235","listItem.0","paragraph.0","strong.0"],"code":"**Array**"},{"id":"/root/children/235/children/0/children/0/children/0/children/0","type":"text","loc":{"start":44590,"end":44595,"line":{"s":1203,"e":1203,"code":["- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),"]},"column":{"s":4,"e":9}},"dim":["","list.235","listItem.0","paragraph.0","strong.0","text.0"],"code":"Array"},{"id":"/root/children/235/children/0/children/0/children/1","type":"text","loc":{"start":44597,"end":44629,"line":{"s":1203,"e":1203,"code":["- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),"]},"column":{"s":11,"e":43}},"dim":["","list.235","listItem.0","paragraph.0","text.1"],"code":" — mapped item-by-item (objects "},{"id":"/root/children/235/children/0/children/0/children/2","type":"inlineCode","loc":{"start":44629,"end":44645,"line":{"s":1203,"e":1203,"code":["- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),"]},"column":{"s":43,"e":59}},"dim":["","list.235","listItem.0","paragraph.0","inlineCode.2"],"code":"`JSON.stringify`"},{"id":"/root/children/235/children/0/children/0/children/3","type":"text","loc":{"start":44645,"end":44658,"line":{"s":1203,"e":1203,"code":["- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),"]},"column":{"s":59,"e":72}},"dim":["","list.235","listItem.0","paragraph.0","text.3"],"code":", primitives "},{"id":"/root/children/235/children/0/children/0/children/4","type":"inlineCode","loc":{"start":44658,"end":44666,"line":{"s":1203,"e":1203,"code":["- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),"]},"column":{"s":72,"e":80}},"dim":["","list.235","listItem.0","paragraph.0","inlineCode.4"],"code":"`String`"},{"id":"/root/children/235/children/0/children/0/children/5","type":"text","loc":{"start":44666,"end":44683,"line":{"s":1203,"e":1204,"code":["- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),","  joined with `\"\\n\"`"]},"column":{"s":80,"e":14}},"dim":["","list.235","listItem.0","paragraph.0","text.5"],"code":"),\n  joined with "},{"id":"/root/children/235/children/0/children/0/children/6","type":"inlineCode","loc":{"start":44683,"end":44689,"line":{"s":1204,"e":1204,"code":["  joined with `\"\\n\"`"]},"column":{"s":14,"e":20}},"dim":["","list.235","listItem.0","paragraph.0","inlineCode.6"],"code":"`\"\\n\"`"},{"id":"/root/children/235/children/1","type":"listItem","loc":{"start":44690,"end":44733,"line":{"s":1205,"e":1205,"code":["- **Object (non-array)** — `JSON.stringify`"]},"column":{"s":0,"e":43}},"dim":["","list.235","listItem.1"],"code":"- **Object (non-array)** — `JSON.stringify`"},{"id":"/root/children/235/children/1/children/0","type":"paragraph","loc":{"start":44692,"end":44733,"line":{"s":1205,"e":1205,"code":["- **Object (non-array)** — `JSON.stringify`"]},"column":{"s":2,"e":43}},"dim":["","list.235","listItem.1","paragraph.0"],"code":"**Object (non-array)** — `JSON.stringify`"},{"id":"/root/children/235/children/1/children/0/children/0","type":"strong","loc":{"start":44692,"end":44714,"line":{"s":1205,"e":1205,"code":["- **Object (non-array)** — `JSON.stringify`"]},"column":{"s":2,"e":24}},"dim":["","list.235","listItem.1","paragraph.0","strong.0"],"code":"**Object (non-array)**"},{"id":"/root/children/235/children/1/children/0/children/0/children/0","type":"text","loc":{"start":44694,"end":44712,"line":{"s":1205,"e":1205,"code":["- **Object (non-array)** — `JSON.stringify`"]},"column":{"s":4,"e":22}},"dim":["","list.235","listItem.1","paragraph.0","strong.0","text.0"],"code":"Object (non-array)"},{"id":"/root/children/235/children/1/children/0/children/1","type":"text","loc":{"start":44714,"end":44717,"line":{"s":1205,"e":1205,"code":["- **Object (non-array)** — `JSON.stringify`"]},"column":{"s":24,"e":27}},"dim":["","list.235","listItem.1","paragraph.0","text.1"],"code":" — "},{"id":"/root/children/235/children/1/children/0/children/2","type":"inlineCode","loc":{"start":44717,"end":44733,"line":{"s":1205,"e":1205,"code":["- **Object (non-array)** — `JSON.stringify`"]},"column":{"s":27,"e":43}},"dim":["","list.235","listItem.1","paragraph.0","inlineCode.2"],"code":"`JSON.stringify`"},{"id":"/root/children/235/children/2","type":"listItem","loc":{"start":44734,"end":44762,"line":{"s":1206,"e":1206,"code":["- **Primitive** — `String()`"]},"column":{"s":0,"e":28}},"dim":["","list.235","listItem.2"],"code":"- **Primitive** — `String()`"},{"id":"/root/children/235/children/2/children/0","type":"paragraph","loc":{"start":44736,"end":44762,"line":{"s":1206,"e":1206,"code":["- **Primitive** — `String()`"]},"column":{"s":2,"e":28}},"dim":["","list.235","listItem.2","paragraph.0"],"code":"**Primitive** — `String()`"},{"id":"/root/children/235/children/2/children/0/children/0","type":"strong","loc":{"start":44736,"end":44749,"line":{"s":1206,"e":1206,"code":["- **Primitive** — `String()`"]},"column":{"s":2,"e":15}},"dim":["","list.235","listItem.2","paragraph.0","strong.0"],"code":"**Primitive**"},{"id":"/root/children/235/children/2/children/0/children/0/children/0","type":"text","loc":{"start":44738,"end":44747,"line":{"s":1206,"e":1206,"code":["- **Primitive** — `String()`"]},"column":{"s":4,"e":13}},"dim":["","list.235","listItem.2","paragraph.0","strong.0","text.0"],"code":"Primitive"},{"id":"/root/children/235/children/2/children/0/children/1","type":"text","loc":{"start":44749,"end":44752,"line":{"s":1206,"e":1206,"code":["- **Primitive** — `String()`"]},"column":{"s":15,"e":18}},"dim":["","list.235","listItem.2","paragraph.0","text.1"],"code":" — "},{"id":"/root/children/235/children/2/children/0/children/2","type":"inlineCode","loc":{"start":44752,"end":44762,"line":{"s":1206,"e":1206,"code":["- **Primitive** — `String()`"]},"column":{"s":18,"e":28}},"dim":["","list.235","listItem.2","paragraph.0","inlineCode.2"],"code":"`String()`"},{"id":"/root/children/236","type":"paragraph","loc":{"start":44764,"end":44873,"line":{"s":1208,"e":1209,"code":["This prevents `[object Object]` output when extruction bodies return arrays or","objects (e.g. search results)."]},"column":{"s":0,"e":30}},"dim":["","paragraph.236"],"code":"This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results)."},{"id":"/root/children/236/children/0","type":"text","loc":{"start":44764,"end":44778,"line":{"s":1208,"e":1208,"code":["This prevents `[object Object]` output when extruction bodies return arrays or"]},"column":{"s":0,"e":14}},"dim":["","paragraph.236","text.0"],"code":"This prevents "},{"id":"/root/children/236/children/1","type":"inlineCode","loc":{"start":44778,"end":44795,"line":{"s":1208,"e":1208,"code":["This prevents `[object Object]` output when extruction bodies return arrays or"]},"column":{"s":14,"e":31}},"dim":["","paragraph.236","inlineCode.1"],"code":"`[object Object]`"},{"id":"/root/children/236/children/2","type":"text","loc":{"start":44795,"end":44873,"line":{"s":1208,"e":1209,"code":["This prevents `[object Object]` output when extruction bodies return arrays or","objects (e.g. search results)."]},"column":{"s":31,"e":30}},"dim":["","paragraph.236","text.2"],"code":" output when extruction bodies return arrays or\nobjects (e.g. search results)."},{"id":"/root/children/237","type":"heading","loc":{"start":44875,"end":44885,"line":{"s":1211,"e":1211,"code":["### Probes"]},"column":{"s":0,"e":10}},"dim":["","heading.237"],"code":"### Probes","symbName":"heading","symbRange":[44887,45407],"symbRangeL":[1211,1223],"outerCode":"\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.","outerHtml":"\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>"},{"id":"/root/children/237/children/0","type":"text","loc":{"start":44879,"end":44885,"line":{"s":1211,"e":1211,"code":["### Probes"]},"column":{"s":4,"e":10}},"dim":["","heading.237","text.0"],"code":"Probes"},{"id":"/root/children/238","type":"paragraph","loc":{"start":44887,"end":44964,"line":{"s":1213,"e":1213,"code":["Two `console.log` probes are placed at the extruction result handling points:"]},"column":{"s":0,"e":77}},"dim":["","paragraph.238"],"code":"Two `console.log` probes are placed at the extruction result handling points:"},{"id":"/root/children/238/children/0","type":"text","loc":{"start":44887,"end":44891,"line":{"s":1213,"e":1213,"code":["Two `console.log` probes are placed at the extruction result handling points:"]},"column":{"s":0,"e":4}},"dim":["","paragraph.238","text.0"],"code":"Two "},{"id":"/root/children/238/children/1","type":"inlineCode","loc":{"start":44891,"end":44904,"line":{"s":1213,"e":1213,"code":["Two `console.log` probes are placed at the extruction result handling points:"]},"column":{"s":4,"e":17}},"dim":["","paragraph.238","inlineCode.1"],"code":"`console.log`"},{"id":"/root/children/238/children/2","type":"text","loc":{"start":44904,"end":44964,"line":{"s":1213,"e":1213,"code":["Two `console.log` probes are placed at the extruction result handling points:"]},"column":{"s":17,"e":77}},"dim":["","paragraph.238","text.2"],"code":" probes are placed at the extruction result handling points:"},{"id":"/root/children/239","type":"list","loc":{"start":44966,"end":45205,"line":{"s":1215,"e":1218,"code":["- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns","  for a non-root extruction. Logs `{ heading, result, hasInsert }`.","- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level","  extructions."]},"column":{"s":0,"e":14}},"dim":["","list.239"],"code":"- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.","symbName":"list","symbRange":[45207,45894],"symbRangeL":[1215,1242],"outerCode":"  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:","outerHtml":"<p>  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</p><ul><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>"},{"id":"/root/children/239/children/0","type":"listItem","loc":{"start":44966,"end":45110,"line":{"s":1215,"e":1216,"code":["- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns","  for a non-root extruction. Logs `{ heading, result, hasInsert }`."]},"column":{"s":0,"e":67}},"dim":["","list.239","listItem.0"],"code":"- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`."},{"id":"/root/children/239/children/0/children/0","type":"paragraph","loc":{"start":44968,"end":45110,"line":{"s":1215,"e":1216,"code":["- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns","  for a non-root extruction. Logs `{ heading, result, hasInsert }`."]},"column":{"s":2,"e":67}},"dim":["","list.239","listItem.0","paragraph.0"],"code":"`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`."},{"id":"/root/children/239/children/0/children/0/children/0","type":"inlineCode","loc":{"start":44968,"end":44990,"line":{"s":1215,"e":1215,"code":["- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns"]},"column":{"s":2,"e":24}},"dim":["","list.239","listItem.0","paragraph.0","inlineCode.0"],"code":"`probe:mdt-ext-result`"},{"id":"/root/children/239/children/0/children/0/children/1","type":"text","loc":{"start":44990,"end":44996,"line":{"s":1215,"e":1215,"code":["- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns"]},"column":{"s":24,"e":30}},"dim":["","list.239","listItem.0","paragraph.0","text.1"],"code":" — in "},{"id":"/root/children/239/children/0/children/0/children/2","type":"inlineCode","loc":{"start":44996,"end":45014,"line":{"s":1215,"e":1215,"code":["- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns"]},"column":{"s":30,"e":48}},"dim":["","list.239","listItem.0","paragraph.0","inlineCode.2"],"code":"`expandChildren()`"},{"id":"/root/children/239/children/0/children/0/children/3","type":"text","loc":{"start":45014,"end":45077,"line":{"s":1215,"e":1216,"code":["- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns","  for a non-root extruction. Logs `{ heading, result, hasInsert }`."]},"column":{"s":48,"e":34}},"dim":["","list.239","listItem.0","paragraph.0","text.3"],"code":", fires after evalFn returns\n  for a non-root extruction. Logs "},{"id":"/root/children/239/children/0/children/0/children/4","type":"inlineCode","loc":{"start":45077,"end":45109,"line":{"s":1216,"e":1216,"code":["  for a non-root extruction. Logs `{ heading, result, hasInsert }`."]},"column":{"s":34,"e":66}},"dim":["","list.239","listItem.0","paragraph.0","inlineCode.4"],"code":"`{ heading, result, hasInsert }`"},{"id":"/root/children/239/children/0/children/0/children/5","type":"text","loc":{"start":45109,"end":45110,"line":{"s":1216,"e":1216,"code":["  for a non-root extruction. Logs `{ heading, result, hasInsert }`."]},"column":{"s":66,"e":67}},"dim":["","list.239","listItem.0","paragraph.0","text.5"],"code":"."},{"id":"/root/children/239/children/1","type":"listItem","loc":{"start":45111,"end":45205,"line":{"s":1217,"e":1218,"code":["- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level","  extructions."]},"column":{"s":0,"e":14}},"dim":["","list.239","listItem.1"],"code":"- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions."},{"id":"/root/children/239/children/1/children/0","type":"paragraph","loc":{"start":45113,"end":45205,"line":{"s":1217,"e":1218,"code":["- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level","  extructions."]},"column":{"s":2,"e":14}},"dim":["","list.239","listItem.1","paragraph.0"],"code":"`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions."},{"id":"/root/children/239/children/1/children/0/children/0","type":"inlineCode","loc":{"start":45113,"end":45140,"line":{"s":1217,"e":1217,"code":["- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level"]},"column":{"s":2,"e":29}},"dim":["","list.239","listItem.1","paragraph.0","inlineCode.0"],"code":"`probe:mdt-ext-root-result`"},{"id":"/root/children/239/children/1/children/0/children/1","type":"text","loc":{"start":45140,"end":45205,"line":{"s":1217,"e":1218,"code":["- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level","  extructions."]},"column":{"s":29,"e":14}},"dim":["","list.239","listItem.1","paragraph.0","text.1"],"code":" — in the root iterator, same shape for root-level\n  extructions."},{"id":"/root/children/240","type":"paragraph","loc":{"start":45207,"end":45407,"line":{"s":1220,"e":1222,"code":["These are the frontend equivalent of the backend probe pattern","(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend","module without PROXY access, so `console.log` is used directly."]},"column":{"s":0,"e":63}},"dim":["","paragraph.240"],"code":"These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly."},{"id":"/root/children/240/children/0","type":"text","loc":{"start":45207,"end":45271,"line":{"s":1220,"e":1221,"code":["These are the frontend equivalent of the backend probe pattern","(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend"]},"column":{"s":0,"e":1}},"dim":["","paragraph.240","text.0"],"code":"These are the frontend equivalent of the backend probe pattern\n("},{"id":"/root/children/240/children/1","type":"inlineCode","loc":{"start":45271,"end":45306,"line":{"s":1221,"e":1221,"code":["(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend"]},"column":{"s":1,"e":36}},"dim":["","paragraph.240","inlineCode.1"],"code":"`PROXY.remoteState?.log({ label })`"},{"id":"/root/children/240/children/2","type":"text","loc":{"start":45306,"end":45376,"line":{"s":1221,"e":1222,"code":["(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend","module without PROXY access, so `console.log` is used directly."]},"column":{"s":36,"e":32}},"dim":["","paragraph.240","text.2"],"code":"). The MDT library is a pure frontend\nmodule without PROXY access, so "},{"id":"/root/children/240/children/3","type":"inlineCode","loc":{"start":45376,"end":45389,"line":{"s":1222,"e":1222,"code":["module without PROXY access, so `console.log` is used directly."]},"column":{"s":32,"e":45}},"dim":["","paragraph.240","inlineCode.3"],"code":"`console.log`"},{"id":"/root/children/240/children/4","type":"text","loc":{"start":45389,"end":45407,"line":{"s":1222,"e":1222,"code":["module without PROXY access, so `console.log` is used directly."]},"column":{"s":45,"e":63}},"dim":["","paragraph.240","text.4"],"code":" is used directly."},{"id":"/root/children/241","type":"heading","loc":{"start":45409,"end":45426,"line":{"s":1224,"e":1224,"code":["## Search Adapter"]},"column":{"s":0,"e":17}},"dim":["","heading.241"],"code":"## Search Adapter","symbName":"heading","symbRange":[45428,45614],"symbRangeL":[1224,1229],"outerCode":"\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.","outerHtml":"\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>"},{"id":"/root/children/241/children/0","type":"text","loc":{"start":45412,"end":45426,"line":{"s":1224,"e":1224,"code":["## Search Adapter"]},"column":{"s":3,"e":17}},"dim":["","heading.241","text.0"],"code":"Search Adapter"},{"id":"/root/children/242","type":"paragraph","loc":{"start":45428,"end":45614,"line":{"s":1226,"e":1228,"code":["The MDT library provides a search adapter that wraps the app's `glassSearchRun()`","with proper async completion detection, emitting per-source events and a","final `allCompletedDone` event."]},"column":{"s":0,"e":31}},"dim":["","paragraph.242"],"code":"The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event."},{"id":"/root/children/242/children/0","type":"text","loc":{"start":45428,"end":45491,"line":{"s":1226,"e":1226,"code":["The MDT library provides a search adapter that wraps the app's `glassSearchRun()`"]},"column":{"s":0,"e":63}},"dim":["","paragraph.242","text.0"],"code":"The MDT library provides a search adapter that wraps the app's "},{"id":"/root/children/242/children/1","type":"inlineCode","loc":{"start":45491,"end":45509,"line":{"s":1226,"e":1226,"code":["The MDT library provides a search adapter that wraps the app's `glassSearchRun()`"]},"column":{"s":63,"e":81}},"dim":["","paragraph.242","inlineCode.1"],"code":"`glassSearchRun()`"},{"id":"/root/children/242/children/2","type":"text","loc":{"start":45509,"end":45589,"line":{"s":1226,"e":1228,"code":["The MDT library provides a search adapter that wraps the app's `glassSearchRun()`","with proper async completion detection, emitting per-source events and a","final `allCompletedDone` event."]},"column":{"s":81,"e":6}},"dim":["","paragraph.242","text.2"],"code":"\nwith proper async completion detection, emitting per-source events and a\nfinal "},{"id":"/root/children/242/children/3","type":"inlineCode","loc":{"start":45589,"end":45607,"line":{"s":1228,"e":1228,"code":["final `allCompletedDone` event."]},"column":{"s":6,"e":24}},"dim":["","paragraph.242","inlineCode.3"],"code":"`allCompletedDone`"},{"id":"/root/children/242/children/4","type":"text","loc":{"start":45607,"end":45614,"line":{"s":1228,"e":1228,"code":["final `allCompletedDone` event."]},"column":{"s":24,"e":31}},"dim":["","paragraph.242","text.4"],"code":" event."},{"id":"/root/children/243","type":"heading","loc":{"start":45616,"end":45639,"line":{"s":1230,"e":1230,"code":["### glassSearchRunAsync"]},"column":{"s":0,"e":23}},"dim":["","heading.243"],"code":"### glassSearchRunAsync","symbName":"heading","symbRange":[45641,46937],"symbRangeL":[1230,1280],"outerCode":"\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```","outerHtml":"\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>"},{"id":"/root/children/243/children/0","type":"text","loc":{"start":45620,"end":45639,"line":{"s":1230,"e":1230,"code":["### glassSearchRunAsync"]},"column":{"s":4,"e":23}},"dim":["","heading.243","text.0"],"code":"glassSearchRunAsync"},{"id":"/root/children/244","type":"paragraph","loc":{"start":45641,"end":45728,"line":{"s":1232,"e":1233,"code":["`mdt/glass-search-run.js` exports an async wrapper around the app's","`glassSearchRun()`:"]},"column":{"s":0,"e":19}},"dim":["","paragraph.244"],"code":"`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:"},{"id":"/root/children/244/children/0","type":"inlineCode","loc":{"start":45641,"end":45666,"line":{"s":1232,"e":1232,"code":["`mdt/glass-search-run.js` exports an async wrapper around the app's"]},"column":{"s":0,"e":25}},"dim":["","paragraph.244","inlineCode.0"],"code":"`mdt/glass-search-run.js`"},{"id":"/root/children/244/children/1","type":"text","loc":{"start":45666,"end":45709,"line":{"s":1232,"e":1233,"code":["`mdt/glass-search-run.js` exports an async wrapper around the app's","`glassSearchRun()`:"]},"column":{"s":25,"e":0}},"dim":["","paragraph.244","text.1"],"code":" exports an async wrapper around the app's\n"},{"id":"/root/children/244/children/2","type":"inlineCode","loc":{"start":45709,"end":45727,"line":{"s":1233,"e":1233,"code":["`glassSearchRun()`:"]},"column":{"s":0,"e":18}},"dim":["","paragraph.244","inlineCode.2"],"code":"`glassSearchRun()`"},{"id":"/root/children/244/children/3","type":"text","loc":{"start":45727,"end":45728,"line":{"s":1233,"e":1233,"code":["`glassSearchRun()`:"]},"column":{"s":18,"e":19}},"dim":["","paragraph.244","text.3"],"code":":"},{"id":"/root/children/245","type":"code","loc":{"start":45731,"end":45880,"line":{"s":1236,"e":1239,"code":["```","glassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)","  → { onSource(fn), onComplete(fn), then(resolve, reject) }","```"]},"column":{"s":0,"e":3}},"dim":["","code.245"],"code":"```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```","symbName":"code","symbRange":[45882,46508],"symbRangeL":[null,1254],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>"},{"id":"/root/children/246","type":"paragraph","loc":{"start":45882,"end":45894,"line":{"s":1241,"e":1241,"code":["The wrapper:"]},"column":{"s":0,"e":12}},"dim":["","paragraph.246"],"code":"The wrapper:"},{"id":"/root/children/246/children/0","type":"text","loc":{"start":45882,"end":45894,"line":{"s":1241,"e":1241,"code":["The wrapper:"]},"column":{"s":0,"e":12}},"dim":["","paragraph.246","text.0"],"code":"The wrapper:"},{"id":"/root/children/247","type":"list","loc":{"start":45896,"end":46424,"line":{"s":1243,"e":1251,"code":["1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is","   irrelevant for programmatic use)","2. Wraps `proxy.addResultItems` to emit `source` events — each call to","   `addResultItems` fires `onSource(items)` with the incoming results","3. Detects completion via a 50ms batch timer after the last `addResultItems` call,","   then fires `onComplete(allResults)`","4. Handles sync-only sources (files/map) by resolving on the next microtick via","   `setTimeout(0)`","5. Has a 5-second safety fallback for async sources"]},"column":{"s":0,"e":51}},"dim":["","list.247"],"code":"1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources","symbName":"list","symbRange":[46426,47452],"symbRangeL":[1243,1298],"outerCode":"   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:","outerHtml":"<p>   irrelevant for programmatic use)</p><ol><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>"},{"id":"/root/children/247/children/0","type":"listItem","loc":{"start":45896,"end":46010,"line":{"s":1243,"e":1244,"code":["1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is","   irrelevant for programmatic use)"]},"column":{"s":0,"e":35}},"dim":["","list.247","listItem.0"],"code":"1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)"},{"id":"/root/children/247/children/0/children/0","type":"paragraph","loc":{"start":45899,"end":46010,"line":{"s":1243,"e":1244,"code":["1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is","   irrelevant for programmatic use)"]},"column":{"s":3,"e":35}},"dim":["","list.247","listItem.0","paragraph.0"],"code":"Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)"},{"id":"/root/children/247/children/0/children/0/children/0","type":"text","loc":{"start":45899,"end":45913,"line":{"s":1243,"e":1243,"code":["1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is"]},"column":{"s":3,"e":17}},"dim":["","list.247","listItem.0","paragraph.0","text.0"],"code":"Passes a mock "},{"id":"/root/children/247/children/0/children/0/children/1","type":"inlineCode","loc":{"start":45913,"end":45924,"line":{"s":1243,"e":1243,"code":["1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is"]},"column":{"s":17,"e":28}},"dim":["","list.247","listItem.0","paragraph.0","inlineCode.1"],"code":"`menuInput`"},{"id":"/root/children/247/children/0/children/0/children/2","type":"text","loc":{"start":45924,"end":45928,"line":{"s":1243,"e":1243,"code":["1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is"]},"column":{"s":28,"e":32}},"dim":["","list.247","listItem.0","paragraph.0","text.2"],"code":" to "},{"id":"/root/children/247/children/0/children/0/children/3","type":"inlineCode","loc":{"start":45928,"end":45944,"line":{"s":1243,"e":1243,"code":["1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is"]},"column":{"s":32,"e":48}},"dim":["","list.247","listItem.0","paragraph.0","inlineCode.3"],"code":"`glassSearchRun`"},{"id":"/root/children/247/children/0/children/0/children/4","type":"text","loc":{"start":45944,"end":46010,"line":{"s":1243,"e":1244,"code":["1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is","   irrelevant for programmatic use)"]},"column":{"s":48,"e":35}},"dim":["","list.247","listItem.0","paragraph.0","text.4"],"code":" (the autocomplete instance is\n   irrelevant for programmatic use)"},{"id":"/root/children/247/children/1","type":"listItem","loc":{"start":46011,"end":46151,"line":{"s":1245,"e":1246,"code":["2. Wraps `proxy.addResultItems` to emit `source` events — each call to","   `addResultItems` fires `onSource(items)` with the incoming results"]},"column":{"s":0,"e":69}},"dim":["","list.247","listItem.1"],"code":"2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results"},{"id":"/root/children/247/children/1/children/0","type":"paragraph","loc":{"start":46014,"end":46151,"line":{"s":1245,"e":1246,"code":["2. Wraps `proxy.addResultItems` to emit `source` events — each call to","   `addResultItems` fires `onSource(items)` with the incoming results"]},"column":{"s":3,"e":69}},"dim":["","list.247","listItem.1","paragraph.0"],"code":"Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results"},{"id":"/root/children/247/children/1/children/0/children/0","type":"text","loc":{"start":46014,"end":46020,"line":{"s":1245,"e":1245,"code":["2. Wraps `proxy.addResultItems` to emit `source` events — each call to"]},"column":{"s":3,"e":9}},"dim":["","list.247","listItem.1","paragraph.0","text.0"],"code":"Wraps "},{"id":"/root/children/247/children/1/children/0/children/1","type":"inlineCode","loc":{"start":46020,"end":46042,"line":{"s":1245,"e":1245,"code":["2. Wraps `proxy.addResultItems` to emit `source` events — each call to"]},"column":{"s":9,"e":31}},"dim":["","list.247","listItem.1","paragraph.0","inlineCode.1"],"code":"`proxy.addResultItems`"},{"id":"/root/children/247/children/1/children/0/children/2","type":"text","loc":{"start":46042,"end":46051,"line":{"s":1245,"e":1245,"code":["2. Wraps `proxy.addResultItems` to emit `source` events — each call to"]},"column":{"s":31,"e":40}},"dim":["","list.247","listItem.1","paragraph.0","text.2"],"code":" to emit "},{"id":"/root/children/247/children/1/children/0/children/3","type":"inlineCode","loc":{"start":46051,"end":46059,"line":{"s":1245,"e":1245,"code":["2. Wraps `proxy.addResultItems` to emit `source` events — each call to"]},"column":{"s":40,"e":48}},"dim":["","list.247","listItem.1","paragraph.0","inlineCode.3"],"code":"`source`"},{"id":"/root/children/247/children/1/children/0/children/4","type":"text","loc":{"start":46059,"end":46082,"line":{"s":1245,"e":1246,"code":["2. Wraps `proxy.addResultItems` to emit `source` events — each call to","   `addResultItems` fires `onSource(items)` with the incoming results"]},"column":{"s":48,"e":0}},"dim":["","list.247","listItem.1","paragraph.0","text.4"],"code":" events — each call to\n"},{"id":"/root/children/247/children/1/children/0/children/5","type":"inlineCode","loc":{"start":46085,"end":46101,"line":{"s":1246,"e":1246,"code":["   `addResultItems` fires `onSource(items)` with the incoming results"]},"column":{"s":3,"e":19}},"dim":["","list.247","listItem.1","paragraph.0","inlineCode.5"],"code":"`addResultItems`"},{"id":"/root/children/247/children/1/children/0/children/6","type":"text","loc":{"start":46101,"end":46108,"line":{"s":1246,"e":1246,"code":["   `addResultItems` fires `onSource(items)` with the incoming results"]},"column":{"s":19,"e":26}},"dim":["","list.247","listItem.1","paragraph.0","text.6"],"code":" fires "},{"id":"/root/children/247/children/1/children/0/children/7","type":"inlineCode","loc":{"start":46108,"end":46125,"line":{"s":1246,"e":1246,"code":["   `addResultItems` fires `onSource(items)` with the incoming results"]},"column":{"s":26,"e":43}},"dim":["","list.247","listItem.1","paragraph.0","inlineCode.7"],"code":"`onSource(items)`"},{"id":"/root/children/247/children/1/children/0/children/8","type":"text","loc":{"start":46125,"end":46151,"line":{"s":1246,"e":1246,"code":["   `addResultItems` fires `onSource(items)` with the incoming results"]},"column":{"s":43,"e":69}},"dim":["","list.247","listItem.1","paragraph.0","text.8"],"code":" with the incoming results"},{"id":"/root/children/247/children/2","type":"listItem","loc":{"start":46152,"end":46273,"line":{"s":1247,"e":1248,"code":["3. Detects completion via a 50ms batch timer after the last `addResultItems` call,","   then fires `onComplete(allResults)`"]},"column":{"s":0,"e":38}},"dim":["","list.247","listItem.2"],"code":"3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`"},{"id":"/root/children/247/children/2/children/0","type":"paragraph","loc":{"start":46155,"end":46273,"line":{"s":1247,"e":1248,"code":["3. Detects completion via a 50ms batch timer after the last `addResultItems` call,","   then fires `onComplete(allResults)`"]},"column":{"s":3,"e":38}},"dim":["","list.247","listItem.2","paragraph.0"],"code":"Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`"},{"id":"/root/children/247/children/2/children/0/children/0","type":"text","loc":{"start":46155,"end":46212,"line":{"s":1247,"e":1247,"code":["3. Detects completion via a 50ms batch timer after the last `addResultItems` call,"]},"column":{"s":3,"e":60}},"dim":["","list.247","listItem.2","paragraph.0","text.0"],"code":"Detects completion via a 50ms batch timer after the last "},{"id":"/root/children/247/children/2/children/0/children/1","type":"inlineCode","loc":{"start":46212,"end":46228,"line":{"s":1247,"e":1247,"code":["3. Detects completion via a 50ms batch timer after the last `addResultItems` call,"]},"column":{"s":60,"e":76}},"dim":["","list.247","listItem.2","paragraph.0","inlineCode.1"],"code":"`addResultItems`"},{"id":"/root/children/247/children/2/children/0/children/2","type":"text","loc":{"start":46228,"end":46249,"line":{"s":1247,"e":1248,"code":["3. Detects completion via a 50ms batch timer after the last `addResultItems` call,","   then fires `onComplete(allResults)`"]},"column":{"s":76,"e":14}},"dim":["","list.247","listItem.2","paragraph.0","text.2"],"code":" call,\n   then fires "},{"id":"/root/children/247/children/2/children/0/children/3","type":"inlineCode","loc":{"start":46249,"end":46273,"line":{"s":1248,"e":1248,"code":["   then fires `onComplete(allResults)`"]},"column":{"s":14,"e":38}},"dim":["","list.247","listItem.2","paragraph.0","inlineCode.3"],"code":"`onComplete(allResults)`"},{"id":"/root/children/247/children/3","type":"listItem","loc":{"start":46274,"end":46372,"line":{"s":1249,"e":1250,"code":["4. Handles sync-only sources (files/map) by resolving on the next microtick via","   `setTimeout(0)`"]},"column":{"s":0,"e":18}},"dim":["","list.247","listItem.3"],"code":"4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`"},{"id":"/root/children/247/children/3/children/0","type":"paragraph","loc":{"start":46277,"end":46372,"line":{"s":1249,"e":1250,"code":["4. Handles sync-only sources (files/map) by resolving on the next microtick via","   `setTimeout(0)`"]},"column":{"s":3,"e":18}},"dim":["","list.247","listItem.3","paragraph.0"],"code":"Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`"},{"id":"/root/children/247/children/3/children/0/children/0","type":"text","loc":{"start":46277,"end":46354,"line":{"s":1249,"e":1250,"code":["4. Handles sync-only sources (files/map) by resolving on the next microtick via","   `setTimeout(0)`"]},"column":{"s":3,"e":0}},"dim":["","list.247","listItem.3","paragraph.0","text.0"],"code":"Handles sync-only sources (files/map) by resolving on the next microtick via\n"},{"id":"/root/children/247/children/3/children/0/children/1","type":"inlineCode","loc":{"start":46357,"end":46372,"line":{"s":1250,"e":1250,"code":["   `setTimeout(0)`"]},"column":{"s":3,"e":18}},"dim":["","list.247","listItem.3","paragraph.0","inlineCode.1"],"code":"`setTimeout(0)`"},{"id":"/root/children/247/children/4","type":"listItem","loc":{"start":46373,"end":46424,"line":{"s":1251,"e":1251,"code":["5. Has a 5-second safety fallback for async sources"]},"column":{"s":0,"e":51}},"dim":["","list.247","listItem.4"],"code":"5. Has a 5-second safety fallback for async sources"},{"id":"/root/children/247/children/4/children/0","type":"paragraph","loc":{"start":46376,"end":46424,"line":{"s":1251,"e":1251,"code":["5. Has a 5-second safety fallback for async sources"]},"column":{"s":3,"e":51}},"dim":["","list.247","listItem.4","paragraph.0"],"code":"Has a 5-second safety fallback for async sources"},{"id":"/root/children/247/children/4/children/0/children/0","type":"text","loc":{"start":46376,"end":46424,"line":{"s":1251,"e":1251,"code":["5. Has a 5-second safety fallback for async sources"]},"column":{"s":3,"e":51}},"dim":["","list.247","listItem.4","paragraph.0","text.0"],"code":"Has a 5-second safety fallback for async sources"},{"id":"/root/children/248","type":"paragraph","loc":{"start":46426,"end":46508,"line":{"s":1253,"e":1253,"code":["Returns a **thenable** object — supports both event-based and Promise-based usage:"]},"column":{"s":0,"e":82}},"dim":["","paragraph.248"],"code":"Returns a **thenable** object — supports both event-based and Promise-based usage:"},{"id":"/root/children/248/children/0","type":"text","loc":{"start":46426,"end":46436,"line":{"s":1253,"e":1253,"code":["Returns a **thenable** object — supports both event-based and Promise-based usage:"]},"column":{"s":0,"e":10}},"dim":["","paragraph.248","text.0"],"code":"Returns a "},{"id":"/root/children/248/children/1","type":"strong","loc":{"start":46436,"end":46448,"line":{"s":1253,"e":1253,"code":["Returns a **thenable** object — supports both event-based and Promise-based usage:"]},"column":{"s":10,"e":22}},"dim":["","paragraph.248","strong.1"],"code":"**thenable**"},{"id":"/root/children/248/children/1/children/0","type":"text","loc":{"start":46438,"end":46446,"line":{"s":1253,"e":1253,"code":["Returns a **thenable** object — supports both event-based and Promise-based usage:"]},"column":{"s":12,"e":20}},"dim":["","paragraph.248","strong.1","text.0"],"code":"thenable"},{"id":"/root/children/248/children/2","type":"text","loc":{"start":46448,"end":46508,"line":{"s":1253,"e":1253,"code":["Returns a **thenable** object — supports both event-based and Promise-based usage:"]},"column":{"s":22,"e":82}},"dim":["","paragraph.248","text.2"],"code":" object — supports both event-based and Promise-based usage:"},{"id":"/root/children/249","type":"code","loc":{"start":46510,"end":46937,"line":{"s":1255,"e":1279,"code":["```js","// Event-based","const search = glassSearchRunAsync(","  query,","  ssss,","  state,","  STATE,","  route,","  prevHashRoute,","  proxy,",");","search.onSource((items) => console.log(\"received\", items.length, \"results\"));","search.onComplete((allResults) => console.log(\"all done\", allResults.length));","","// Promise-based","const allResults = await glassSearchRunAsync(","  query,","  ssss,","  state,","  STATE,","  route,","  prevHashRoute,","  proxy,",");","```"]},"column":{"s":0,"e":3}},"dim":["","code.249"],"code":"```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```","symbName":"code","symbRange":[46939,47021],"symbRangeL":[null,1285],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n"},{"id":"/root/children/250","type":"heading","loc":{"start":46939,"end":46959,"line":{"s":1281,"e":1281,"code":["### search() adapter"]},"column":{"s":0,"e":20}},"dim":["","heading.250"],"code":"### search() adapter","symbName":"heading","symbRange":[46961,47204],"symbRangeL":[1281,1292],"outerCode":"\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.","outerHtml":"\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>"},{"id":"/root/children/250/children/0","type":"text","loc":{"start":46943,"end":46959,"line":{"s":1281,"e":1281,"code":["### search() adapter"]},"column":{"s":4,"e":20}},"dim":["","heading.250","text.0"],"code":"search() adapter"},{"id":"/root/children/251","type":"paragraph","loc":{"start":46961,"end":47021,"line":{"s":1283,"e":1283,"code":["`mdt/search-adapter.js` exports a thin convenience function:"]},"column":{"s":0,"e":60}},"dim":["","paragraph.251"],"code":"`mdt/search-adapter.js` exports a thin convenience function:"},{"id":"/root/children/251/children/0","type":"inlineCode","loc":{"start":46961,"end":46984,"line":{"s":1283,"e":1283,"code":["`mdt/search-adapter.js` exports a thin convenience function:"]},"column":{"s":0,"e":23}},"dim":["","paragraph.251","inlineCode.0"],"code":"`mdt/search-adapter.js`"},{"id":"/root/children/251/children/1","type":"text","loc":{"start":46984,"end":47021,"line":{"s":1283,"e":1283,"code":["`mdt/search-adapter.js` exports a thin convenience function:"]},"column":{"s":23,"e":60}},"dim":["","paragraph.251","text.1"],"code":" exports a thin convenience function:"},{"id":"/root/children/252","type":"code","loc":{"start":47024,"end":47105,"line":{"s":1286,"e":1288,"code":["```","search(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable","```"]},"column":{"s":0,"e":3}},"dim":["","code.252"],"code":"```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```","symbName":"code","symbRange":[47107,48518],"symbRangeL":[null,1320],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n"},{"id":"/root/children/253","type":"paragraph","loc":{"start":47107,"end":47204,"line":{"s":1290,"e":1291,"code":["Returns empty results for empty/whitespace queries. Otherwise delegates to","`glassSearchRunAsync`."]},"column":{"s":0,"e":22}},"dim":["","paragraph.253"],"code":"Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`."},{"id":"/root/children/253/children/0","type":"text","loc":{"start":47107,"end":47182,"line":{"s":1290,"e":1291,"code":["Returns empty results for empty/whitespace queries. Otherwise delegates to","`glassSearchRunAsync`."]},"column":{"s":0,"e":0}},"dim":["","paragraph.253","text.0"],"code":"Returns empty results for empty/whitespace queries. Otherwise delegates to\n"},{"id":"/root/children/253/children/1","type":"inlineCode","loc":{"start":47182,"end":47203,"line":{"s":1291,"e":1291,"code":["`glassSearchRunAsync`."]},"column":{"s":0,"e":21}},"dim":["","paragraph.253","inlineCode.1"],"code":"`glassSearchRunAsync`"},{"id":"/root/children/253/children/2","type":"text","loc":{"start":47203,"end":47204,"line":{"s":1291,"e":1291,"code":["`glassSearchRunAsync`."]},"column":{"s":21,"e":22}},"dim":["","paragraph.253","text.2"],"code":"."},{"id":"/root/children/254","type":"heading","loc":{"start":47206,"end":47230,"line":{"s":1293,"e":1293,"code":["### Completion detection"]},"column":{"s":0,"e":24}},"dim":["","heading.254"],"code":"### Completion detection","symbName":"heading","symbRange":[47232,48009],"symbRangeL":[1293,1308],"outerCode":"\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.","outerHtml":"\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>"},{"id":"/root/children/254/children/0","type":"text","loc":{"start":47210,"end":47230,"line":{"s":1293,"e":1293,"code":["### Completion detection"]},"column":{"s":4,"e":24}},"dim":["","heading.254","text.0"],"code":"Completion detection"},{"id":"/root/children/255","type":"paragraph","loc":{"start":47232,"end":47452,"line":{"s":1295,"e":1297,"code":["The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but","kicks off async SQLite fragment searches (debounced at 5ms). The result list","(`resultList` from `glass-search.js`) is populated incrementally:"]},"column":{"s":0,"e":65}},"dim":["","paragraph.255"],"code":"The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:"},{"id":"/root/children/255/children/0","type":"text","loc":{"start":47232,"end":47254,"line":{"s":1295,"e":1295,"code":["The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but"]},"column":{"s":0,"e":22}},"dim":["","paragraph.255","text.0"],"code":"The \"tiny issue\" with "},{"id":"/root/children/255/children/1","type":"inlineCode","loc":{"start":47254,"end":47272,"line":{"s":1295,"e":1295,"code":["The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but"]},"column":{"s":22,"e":40}},"dim":["","paragraph.255","inlineCode.1"],"code":"`glassSearchRun()`"},{"id":"/root/children/255/children/2","type":"text","loc":{"start":47272,"end":47388,"line":{"s":1295,"e":1297,"code":["The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but","kicks off async SQLite fragment searches (debounced at 5ms). The result list","(`resultList` from `glass-search.js`) is populated incrementally:"]},"column":{"s":40,"e":1}},"dim":["","paragraph.255","text.2"],"code":" is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n("},{"id":"/root/children/255/children/3","type":"inlineCode","loc":{"start":47388,"end":47400,"line":{"s":1297,"e":1297,"code":["(`resultList` from `glass-search.js`) is populated incrementally:"]},"column":{"s":1,"e":13}},"dim":["","paragraph.255","inlineCode.3"],"code":"`resultList`"},{"id":"/root/children/255/children/4","type":"text","loc":{"start":47400,"end":47406,"line":{"s":1297,"e":1297,"code":["(`resultList` from `glass-search.js`) is populated incrementally:"]},"column":{"s":13,"e":19}},"dim":["","paragraph.255","text.4"],"code":" from "},{"id":"/root/children/255/children/5","type":"inlineCode","loc":{"start":47406,"end":47423,"line":{"s":1297,"e":1297,"code":["(`resultList` from `glass-search.js`) is populated incrementally:"]},"column":{"s":19,"e":36}},"dim":["","paragraph.255","inlineCode.5"],"code":"`glass-search.js`"},{"id":"/root/children/255/children/6","type":"text","loc":{"start":47423,"end":47452,"line":{"s":1297,"e":1297,"code":["(`resultList` from `glass-search.js`) is populated incrementally:"]},"column":{"s":36,"e":65}},"dim":["","paragraph.255","text.6"],"code":") is populated incrementally:"},{"id":"/root/children/256","type":"list","loc":{"start":47454,"end":47818,"line":{"s":1299,"e":1303,"code":["1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`","2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:","   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +","   `menuInput.rerender()` is called","3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`"]},"column":{"s":0,"e":76}},"dim":["","list.256"],"code":"1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`","symbName":"list","symbRange":[47820,48180],"symbRangeL":[1299,1313],"outerCode":"2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:","outerHtml":"<ol><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>"},{"id":"/root/children/256/children/0","type":"listItem","loc":{"start":47454,"end":47542,"line":{"s":1299,"e":1299,"code":["1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`"]},"column":{"s":0,"e":88}},"dim":["","list.256","listItem.0"],"code":"1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`"},{"id":"/root/children/256/children/0/children/0","type":"paragraph","loc":{"start":47457,"end":47542,"line":{"s":1299,"e":1299,"code":["1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`"]},"column":{"s":3,"e":88}},"dim":["","list.256","listItem.0","paragraph.0"],"code":"**Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`"},{"id":"/root/children/256/children/0/children/0/children/0","type":"strong","loc":{"start":47457,"end":47473,"line":{"s":1299,"e":1299,"code":["1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`"]},"column":{"s":3,"e":19}},"dim":["","list.256","listItem.0","paragraph.0","strong.0"],"code":"**Sync sources**"},{"id":"/root/children/256/children/0/children/0/children/0/children/0","type":"text","loc":{"start":47459,"end":47471,"line":{"s":1299,"e":1299,"code":["1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`"]},"column":{"s":5,"e":17}},"dim":["","list.256","listItem.0","paragraph.0","strong.0","text.0"],"code":"Sync sources"},{"id":"/root/children/256/children/0/children/0/children/1","type":"text","loc":{"start":47473,"end":47504,"line":{"s":1299,"e":1299,"code":["1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`"]},"column":{"s":19,"e":50}},"dim":["","list.256","listItem.0","paragraph.0","text.1"],"code":" (files, map) push directly to "},{"id":"/root/children/256/children/0/children/0/children/2","type":"inlineCode","loc":{"start":47504,"end":47516,"line":{"s":1299,"e":1299,"code":["1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`"]},"column":{"s":50,"e":62}},"dim":["","list.256","listItem.0","paragraph.0","inlineCode.2"],"code":"`resultList`"},{"id":"/root/children/256/children/0/children/0/children/3","type":"text","loc":{"start":47516,"end":47524,"line":{"s":1299,"e":1299,"code":["1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`"]},"column":{"s":62,"e":70}},"dim":["","list.256","listItem.0","paragraph.0","text.3"],"code":" inside "},{"id":"/root/children/256/children/0/children/0/children/4","type":"inlineCode","loc":{"start":47524,"end":47542,"line":{"s":1299,"e":1299,"code":["1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`"]},"column":{"s":70,"e":88}},"dim":["","list.256","listItem.0","paragraph.0","inlineCode.4"],"code":"`searchInRepoJson`"},{"id":"/root/children/256/children/1","type":"listItem","loc":{"start":47543,"end":47741,"line":{"s":1300,"e":1302,"code":["2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:","   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +","   `menuInput.rerender()` is called"]},"column":{"s":0,"e":35}},"dim":["","list.256","listItem.1"],"code":"2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called"},{"id":"/root/children/256/children/1/children/0","type":"paragraph","loc":{"start":47546,"end":47741,"line":{"s":1300,"e":1302,"code":["2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:","   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +","   `menuInput.rerender()` is called"]},"column":{"s":3,"e":35}},"dim":["","list.256","listItem.1","paragraph.0"],"code":"**Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called"},{"id":"/root/children/256/children/1/children/0/children/0","type":"strong","loc":{"start":47546,"end":47574,"line":{"s":1300,"e":1300,"code":["2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:"]},"column":{"s":3,"e":31}},"dim":["","list.256","listItem.1","paragraph.0","strong.0"],"code":"**Debounced SQLite sources**"},{"id":"/root/children/256/children/1/children/0/children/0/children/0","type":"text","loc":{"start":47548,"end":47572,"line":{"s":1300,"e":1300,"code":["2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:"]},"column":{"s":5,"e":29}},"dim":["","list.256","listItem.1","paragraph.0","strong.0","text.0"],"code":"Debounced SQLite sources"},{"id":"/root/children/256/children/1/children/0/children/1","type":"text","loc":{"start":47574,"end":47630,"line":{"s":1300,"e":1301,"code":["2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:","   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +"]},"column":{"s":31,"e":0}},"dim":["","list.256","listItem.1","paragraph.0","text.1"],"code":" (fragments, nodes, maps, content, links) arrive later:\n"},{"id":"/root/children/256/children/1/children/0/children/2","type":"inlineCode","loc":{"start":47633,"end":47652,"line":{"s":1301,"e":1301,"code":["   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +"]},"column":{"s":3,"e":22}},"dim":["","list.256","listItem.1","paragraph.0","inlineCode.2"],"code":"`searchInFragments`"},{"id":"/root/children/256/children/1/children/0/children/3","type":"text","loc":{"start":47652,"end":47655,"line":{"s":1301,"e":1301,"code":["   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +"]},"column":{"s":22,"e":25}},"dim":["","list.256","listItem.1","paragraph.0","text.3"],"code":" → "},{"id":"/root/children/256/children/1/children/0/children/4","type":"inlineCode","loc":{"start":47655,"end":47677,"line":{"s":1301,"e":1301,"code":["   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +"]},"column":{"s":25,"e":47}},"dim":["","list.256","listItem.1","paragraph.0","inlineCode.4"],"code":"`proxy.addResultItems`"},{"id":"/root/children/256/children/1/children/0/children/5","type":"text","loc":{"start":47677,"end":47680,"line":{"s":1301,"e":1301,"code":["   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +"]},"column":{"s":47,"e":50}},"dim":["","list.256","listItem.1","paragraph.0","text.5"],"code":" → "},{"id":"/root/children/256/children/1/children/0/children/6","type":"inlineCode","loc":{"start":47680,"end":47692,"line":{"s":1301,"e":1301,"code":["   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +"]},"column":{"s":50,"e":62}},"dim":["","list.256","listItem.1","paragraph.0","inlineCode.6"],"code":"`resultList`"},{"id":"/root/children/256/children/1/children/0/children/7","type":"text","loc":{"start":47692,"end":47706,"line":{"s":1301,"e":1302,"code":["   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +","   `menuInput.rerender()` is called"]},"column":{"s":62,"e":0}},"dim":["","list.256","listItem.1","paragraph.0","text.7"],"code":" is updated +\n"},{"id":"/root/children/256/children/1/children/0/children/8","type":"inlineCode","loc":{"start":47709,"end":47731,"line":{"s":1302,"e":1302,"code":["   `menuInput.rerender()` is called"]},"column":{"s":3,"e":25}},"dim":["","list.256","listItem.1","paragraph.0","inlineCode.8"],"code":"`menuInput.rerender()`"},{"id":"/root/children/256/children/1/children/0/children/9","type":"text","loc":{"start":47731,"end":47741,"line":{"s":1302,"e":1302,"code":["   `menuInput.rerender()` is called"]},"column":{"s":25,"e":35}},"dim":["","list.256","listItem.1","paragraph.0","text.9"],"code":" is called"},{"id":"/root/children/256/children/2","type":"listItem","loc":{"start":47742,"end":47818,"line":{"s":1303,"e":1303,"code":["3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`"]},"column":{"s":0,"e":76}},"dim":["","list.256","listItem.2"],"code":"3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`"},{"id":"/root/children/256/children/2/children/0","type":"paragraph","loc":{"start":47745,"end":47818,"line":{"s":1303,"e":1303,"code":["3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`"]},"column":{"s":3,"e":76}},"dim":["","list.256","listItem.2","paragraph.0"],"code":"**History source** arrives via `searchInHistory` → `proxy.addResultItems`"},{"id":"/root/children/256/children/2/children/0/children/0","type":"strong","loc":{"start":47745,"end":47763,"line":{"s":1303,"e":1303,"code":["3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`"]},"column":{"s":3,"e":21}},"dim":["","list.256","listItem.2","paragraph.0","strong.0"],"code":"**History source**"},{"id":"/root/children/256/children/2/children/0/children/0/children/0","type":"text","loc":{"start":47747,"end":47761,"line":{"s":1303,"e":1303,"code":["3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`"]},"column":{"s":5,"e":19}},"dim":["","list.256","listItem.2","paragraph.0","strong.0","text.0"],"code":"History source"},{"id":"/root/children/256/children/2/children/0/children/1","type":"text","loc":{"start":47763,"end":47776,"line":{"s":1303,"e":1303,"code":["3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`"]},"column":{"s":21,"e":34}},"dim":["","list.256","listItem.2","paragraph.0","text.1"],"code":" arrives via "},{"id":"/root/children/256/children/2/children/0/children/2","type":"inlineCode","loc":{"start":47776,"end":47793,"line":{"s":1303,"e":1303,"code":["3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`"]},"column":{"s":34,"e":51}},"dim":["","list.256","listItem.2","paragraph.0","inlineCode.2"],"code":"`searchInHistory`"},{"id":"/root/children/256/children/2/children/0/children/3","type":"text","loc":{"start":47793,"end":47796,"line":{"s":1303,"e":1303,"code":["3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`"]},"column":{"s":51,"e":54}},"dim":["","list.256","listItem.2","paragraph.0","text.3"],"code":" → "},{"id":"/root/children/256/children/2/children/0/children/4","type":"inlineCode","loc":{"start":47796,"end":47818,"line":{"s":1303,"e":1303,"code":["3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`"]},"column":{"s":54,"e":76}},"dim":["","list.256","listItem.2","paragraph.0","inlineCode.4"],"code":"`proxy.addResultItems`"},{"id":"/root/children/257","type":"paragraph","loc":{"start":47820,"end":48009,"line":{"s":1305,"e":1307,"code":["The wrapper intercepts `proxy.addResultItems` to know when async results arrive.","A 50ms batch window absorbs cascaded calls, then `onComplete` fires with the","full, deduplicated result list."]},"column":{"s":0,"e":31}},"dim":["","paragraph.257"],"code":"The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list."},{"id":"/root/children/257/children/0","type":"text","loc":{"start":47820,"end":47843,"line":{"s":1305,"e":1305,"code":["The wrapper intercepts `proxy.addResultItems` to know when async results arrive."]},"column":{"s":0,"e":23}},"dim":["","paragraph.257","text.0"],"code":"The wrapper intercepts "},{"id":"/root/children/257/children/1","type":"inlineCode","loc":{"start":47843,"end":47865,"line":{"s":1305,"e":1305,"code":["The wrapper intercepts `proxy.addResultItems` to know when async results arrive."]},"column":{"s":23,"e":45}},"dim":["","paragraph.257","inlineCode.1"],"code":"`proxy.addResultItems`"},{"id":"/root/children/257/children/2","type":"text","loc":{"start":47865,"end":47950,"line":{"s":1305,"e":1306,"code":["The wrapper intercepts `proxy.addResultItems` to know when async results arrive.","A 50ms batch window absorbs cascaded calls, then `onComplete` fires with the"]},"column":{"s":45,"e":49}},"dim":["","paragraph.257","text.2"],"code":" to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then "},{"id":"/root/children/257/children/3","type":"inlineCode","loc":{"start":47950,"end":47962,"line":{"s":1306,"e":1306,"code":["A 50ms batch window absorbs cascaded calls, then `onComplete` fires with the"]},"column":{"s":49,"e":61}},"dim":["","paragraph.257","inlineCode.3"],"code":"`onComplete`"},{"id":"/root/children/257/children/4","type":"text","loc":{"start":47962,"end":48009,"line":{"s":1306,"e":1307,"code":["A 50ms batch window absorbs cascaded calls, then `onComplete` fires with the","full, deduplicated result list."]},"column":{"s":61,"e":31}},"dim":["","paragraph.257","text.4"],"code":" fires with the\nfull, deduplicated result list."},{"id":"/root/children/258","type":"heading","loc":{"start":48011,"end":48029,"line":{"s":1309,"e":1309,"code":["## Adapter Pattern"]},"column":{"s":0,"e":18}},"dim":["","heading.258"],"code":"## Adapter Pattern","symbName":"heading","symbRange":[48031,48637],"symbRangeL":[1309,1325],"outerCode":"\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```","outerHtml":"\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>"},{"id":"/root/children/258/children/0","type":"text","loc":{"start":48014,"end":48029,"line":{"s":1309,"e":1309,"code":["## Adapter Pattern"]},"column":{"s":3,"e":18}},"dim":["","heading.258","text.0"],"code":"Adapter Pattern"},{"id":"/root/children/259","type":"paragraph","loc":{"start":48031,"end":48180,"line":{"s":1311,"e":1312,"code":["Adapters are **functions injected into the runner context** that extruction","bodies can call as if they were local variables. The mechanism is simple:"]},"column":{"s":0,"e":73}},"dim":["","paragraph.259"],"code":"Adapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:"},{"id":"/root/children/259/children/0","type":"text","loc":{"start":48031,"end":48044,"line":{"s":1311,"e":1311,"code":["Adapters are **functions injected into the runner context** that extruction"]},"column":{"s":0,"e":13}},"dim":["","paragraph.259","text.0"],"code":"Adapters are "},{"id":"/root/children/259/children/1","type":"strong","loc":{"start":48044,"end":48090,"line":{"s":1311,"e":1311,"code":["Adapters are **functions injected into the runner context** that extruction"]},"column":{"s":13,"e":59}},"dim":["","paragraph.259","strong.1"],"code":"**functions injected into the runner context**"},{"id":"/root/children/259/children/1/children/0","type":"text","loc":{"start":48046,"end":48088,"line":{"s":1311,"e":1311,"code":["Adapters are **functions injected into the runner context** that extruction"]},"column":{"s":15,"e":57}},"dim":["","paragraph.259","strong.1","text.0"],"code":"functions injected into the runner context"},{"id":"/root/children/259/children/2","type":"text","loc":{"start":48090,"end":48180,"line":{"s":1311,"e":1312,"code":["Adapters are **functions injected into the runner context** that extruction","bodies can call as if they were local variables. The mechanism is simple:"]},"column":{"s":59,"e":73}},"dim":["","paragraph.259","text.2"],"code":" that extruction\nbodies can call as if they were local variables. The mechanism is simple:"},{"id":"/root/children/260","type":"list","loc":{"start":48182,"end":48518,"line":{"s":1314,"e":1318,"code":["1. The runner receives `context = { search, fetchDb, ... }` — keys are names,","   values are functions or data","2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`","   — each context key becomes a named parameter of the compiled function","3. The extruction body can `await adapterName(...)` just like any JS function"]},"column":{"s":0,"e":77}},"dim":["","list.260"],"code":"1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function","symbName":"list","symbRange":[48521,49124],"symbRangeL":[1314,1353],"outerCode":"   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```\n\n### How it works\n\nGiven this setup:\n\n```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```\n\nAn extruction body like:\n\n\n```\n## ${find stuff}\n\n\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```\n\n...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.\n\n### Naming rules","outerHtml":"<p>   values are functions or data</p><ol><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>\n\n<h3>How it works</h3>\n\n<p>Given this setup:</p>\n\n<p>```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```</p>\n\n<p>An extruction body like:</p>\n\n\n<p>```</p><h2>${find stuff}</h2>\n\n<p>\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<p>...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.</p>\n\n<h3>Naming rules</h3>"},{"id":"/root/children/260/children/0","type":"listItem","loc":{"start":48182,"end":48291,"line":{"s":1314,"e":1315,"code":["1. The runner receives `context = { search, fetchDb, ... }` — keys are names,","   values are functions or data"]},"column":{"s":0,"e":31}},"dim":["","list.260","listItem.0"],"code":"1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data"},{"id":"/root/children/260/children/0/children/0","type":"paragraph","loc":{"start":48185,"end":48291,"line":{"s":1314,"e":1315,"code":["1. The runner receives `context = { search, fetchDb, ... }` — keys are names,","   values are functions or data"]},"column":{"s":3,"e":31}},"dim":["","list.260","listItem.0","paragraph.0"],"code":"The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data"},{"id":"/root/children/260/children/0/children/0/children/0","type":"text","loc":{"start":48185,"end":48205,"line":{"s":1314,"e":1314,"code":["1. The runner receives `context = { search, fetchDb, ... }` — keys are names,"]},"column":{"s":3,"e":23}},"dim":["","list.260","listItem.0","paragraph.0","text.0"],"code":"The runner receives "},{"id":"/root/children/260/children/0/children/0/children/1","type":"inlineCode","loc":{"start":48205,"end":48241,"line":{"s":1314,"e":1314,"code":["1. The runner receives `context = { search, fetchDb, ... }` — keys are names,"]},"column":{"s":23,"e":59}},"dim":["","list.260","listItem.0","paragraph.0","inlineCode.1"],"code":"`context = { search, fetchDb, ... }`"},{"id":"/root/children/260/children/0/children/0/children/2","type":"text","loc":{"start":48241,"end":48291,"line":{"s":1314,"e":1315,"code":["1. The runner receives `context = { search, fetchDb, ... }` — keys are names,","   values are functions or data"]},"column":{"s":59,"e":31}},"dim":["","list.260","listItem.0","paragraph.0","text.2"],"code":" — keys are names,\n   values are functions or data"},{"id":"/root/children/260/children/1","type":"listItem","loc":{"start":48292,"end":48440,"line":{"s":1316,"e":1317,"code":["2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`","   — each context key becomes a named parameter of the compiled function"]},"column":{"s":0,"e":72}},"dim":["","list.260","listItem.1"],"code":"2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function"},{"id":"/root/children/260/children/1/children/0","type":"paragraph","loc":{"start":48295,"end":48440,"line":{"s":1316,"e":1317,"code":["2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`","   — each context key becomes a named parameter of the compiled function"]},"column":{"s":3,"e":72}},"dim":["","list.260","listItem.1","paragraph.0"],"code":"`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function"},{"id":"/root/children/260/children/1/children/0/children/0","type":"inlineCode","loc":{"start":48295,"end":48307,"line":{"s":1316,"e":1316,"code":["2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`"]},"column":{"s":3,"e":15}},"dim":["","list.260","listItem.1","paragraph.0","inlineCode.0"],"code":"`evalBody()`"},{"id":"/root/children/260/children/1/children/0/children/1","type":"text","loc":{"start":48307,"end":48313,"line":{"s":1316,"e":1316,"code":["2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`"]},"column":{"s":15,"e":21}},"dim":["","list.260","listItem.1","paragraph.0","text.1"],"code":" uses "},{"id":"/root/children/260/children/1/children/0/children/2","type":"inlineCode","loc":{"start":48313,"end":48367,"line":{"s":1316,"e":1316,"code":["2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`"]},"column":{"s":21,"e":75}},"dim":["","list.260","listItem.1","paragraph.0","inlineCode.2"],"code":"`new AsyncFunction(...Object.keys(context), bodyText)`"},{"id":"/root/children/260/children/1/children/0/children/3","type":"text","loc":{"start":48367,"end":48440,"line":{"s":1316,"e":1317,"code":["2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`","   — each context key becomes a named parameter of the compiled function"]},"column":{"s":75,"e":72}},"dim":["","list.260","listItem.1","paragraph.0","text.3"],"code":"\n   — each context key becomes a named parameter of the compiled function"},{"id":"/root/children/260/children/2","type":"listItem","loc":{"start":48441,"end":48518,"line":{"s":1318,"e":1318,"code":["3. The extruction body can `await adapterName(...)` just like any JS function"]},"column":{"s":0,"e":77}},"dim":["","list.260","listItem.2"],"code":"3. The extruction body can `await adapterName(...)` just like any JS function"},{"id":"/root/children/260/children/2/children/0","type":"paragraph","loc":{"start":48444,"end":48518,"line":{"s":1318,"e":1318,"code":["3. The extruction body can `await adapterName(...)` just like any JS function"]},"column":{"s":3,"e":77}},"dim":["","list.260","listItem.2","paragraph.0"],"code":"The extruction body can `await adapterName(...)` just like any JS function"},{"id":"/root/children/260/children/2/children/0/children/0","type":"text","loc":{"start":48444,"end":48468,"line":{"s":1318,"e":1318,"code":["3. The extruction body can `await adapterName(...)` just like any JS function"]},"column":{"s":3,"e":27}},"dim":["","list.260","listItem.2","paragraph.0","text.0"],"code":"The extruction body can "},{"id":"/root/children/260/children/2/children/0/children/1","type":"inlineCode","loc":{"start":48468,"end":48492,"line":{"s":1318,"e":1318,"code":["3. The extruction body can `await adapterName(...)` just like any JS function"]},"column":{"s":27,"e":51}},"dim":["","list.260","listItem.2","paragraph.0","inlineCode.1"],"code":"`await adapterName(...)`"},{"id":"/root/children/260/children/2/children/0/children/2","type":"text","loc":{"start":48492,"end":48518,"line":{"s":1318,"e":1318,"code":["3. The extruction body can `await adapterName(...)` just like any JS function"]},"column":{"s":51,"e":77}},"dim":["","list.260","listItem.2","paragraph.0","text.2"],"code":" just like any JS function"},{"id":"/root/children/261","type":"code","loc":{"start":48521,"end":48637,"line":{"s":1321,"e":1324,"code":["```","runner(context, { evalFn: evalBody })","//            ^— keys here become parameter names in extruction bodies","```"]},"column":{"s":0,"e":3}},"dim":["","code.261"],"code":"```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```","symbName":"code","symbRange":[48639,48674],"symbRangeL":[null,1329],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```\n\n### How it works\n\nGiven this setup:","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>\n\n<h3>How it works</h3>\n\n<p>Given this setup:</p>"},{"id":"/root/children/262","type":"heading","loc":{"start":48639,"end":48655,"line":{"s":1326,"e":1326,"code":["### How it works"]},"column":{"s":0,"e":16}},"dim":["","heading.262"],"code":"### How it works","symbName":"heading","symbRange":[48657,49106],"symbRangeL":[1326,1351],"outerCode":"\nGiven this setup:\n\n```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```\n\nAn extruction body like:\n\n\n```\n## ${find stuff}\n\n\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```\n\n...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.","outerHtml":"\n<p>Given this setup:</p>\n\n<p>```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```</p>\n\n<p>An extruction body like:</p>\n\n\n<p>```</p><h2>${find stuff}</h2>\n\n<p>\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<p>...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.</p>"},{"id":"/root/children/262/children/0","type":"text","loc":{"start":48643,"end":48655,"line":{"s":1326,"e":1326,"code":["### How it works"]},"column":{"s":4,"e":16}},"dim":["","heading.262","text.0"],"code":"How it works"},{"id":"/root/children/263","type":"paragraph","loc":{"start":48657,"end":48674,"line":{"s":1328,"e":1328,"code":["Given this setup:"]},"column":{"s":0,"e":17}},"dim":["","paragraph.263"],"code":"Given this setup:"},{"id":"/root/children/263/children/0","type":"text","loc":{"start":48657,"end":48674,"line":{"s":1328,"e":1328,"code":["Given this setup:"]},"column":{"s":0,"e":17}},"dim":["","paragraph.263","text.0"],"code":"Given this setup:"},{"id":"/root/children/264","type":"code","loc":{"start":48676,"end":48780,"line":{"s":1330,"e":1335,"code":["```js","const doc = runner(","  { search: mySearchFn, getUser: myGetUserFn },","  { evalFn: evalBody },",");","```"]},"column":{"s":0,"e":3}},"dim":["","code.264"],"code":"```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```","symbName":"code","symbRange":[48782,48806],"symbRangeL":[null,1339],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```\n\n### How it works\n\nGiven this setup:\n\n```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```\n\nAn extruction body like:\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>\n\n<h3>How it works</h3>\n\n<p>Given this setup:</p>\n\n<p>```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```</p>\n\n<p>An extruction body like:</p>\n"},{"id":"/root/children/265","type":"paragraph","loc":{"start":48782,"end":48806,"line":{"s":1337,"e":1337,"code":["An extruction body like:"]},"column":{"s":0,"e":24}},"dim":["","paragraph.265"],"code":"An extruction body like:"},{"id":"/root/children/265/children/0","type":"text","loc":{"start":48782,"end":48806,"line":{"s":1337,"e":1337,"code":["An extruction body like:"]},"column":{"s":0,"e":24}},"dim":["","paragraph.265","text.0"],"code":"An extruction body like:"},{"id":"/root/children/266","type":"code","loc":{"start":48809,"end":48946,"line":{"s":1340,"e":1347,"code":["```","## ${find stuff}","","\\`\\`\\`javascript","const results = await search(\"mdd\")","return insert( results.map(r => r.name).join(\"\\n\"))","\\`\\`\\`","```"]},"column":{"s":0,"e":3}},"dim":["","code.266"],"code":"```\n## ${find stuff}\n\n\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```","symbName":"code","symbRange":[48948,50659],"symbRangeL":[null,1388],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```\n\n### How it works\n\nGiven this setup:\n\n```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```\n\nAn extruction body like:\n\n\n```\n## ${find stuff}\n\n\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```\n\n...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.\n\n### Naming rules\n\n- Keys must be **valid JS identifiers** (no hyphens, no leading digits)\n- Use **camelCase** — this is idiomatic for JS function names\n- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)\n- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`\n\n### Return protocol\n\nAdapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:\n\n- `return insert(value)` — the extruction produces output\n- `return undefined` or no return — extruction stays transparent\n- `throw error` — propagates to the consumer (or caught by `onExtructionError`)\n\nThis means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.\n\n### Adapter conventions\n\n1. **Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.\n\n2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.\n\n3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:\n","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>\n\n<h3>How it works</h3>\n\n<p>Given this setup:</p>\n\n<p>```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```</p>\n\n<p>An extruction body like:</p>\n\n\n<p>```</p><h2>${find stuff}</h2>\n\n<p>\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<p>...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.</p>\n\n<h3>Naming rules</h3>\n\n<ul><li>Keys must be <b>valid JS identifiers</b> (no hyphens, no leading digits)</li><li>Use <b>camelCase</b> — this is idiomatic for JS function names</li><li>Avoid the `<i>mdt</i>` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)</li><li>Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`</li></ul>\n\n<h3>Return protocol</h3>\n\n<p>Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:</p>\n\n<ul><li>`return insert(value)` — the extruction produces output</li><li>`return undefined` or no return — extruction stays transparent</li><li>`throw error` — propagates to the consumer (or caught by `onExtructionError`)</li></ul>\n\n<p>This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.</p>\n\n<h3>Adapter conventions</h3>\n\n<ol><li><b>Async by convention</b> — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.</li></ol>\n\n<ol><li><b>Error handling</b> — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.</li></ol>\n\n<ol><li><b>`_mdt_label`</b> — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:</li></ol>\n"},{"id":"/root/children/267","type":"paragraph","loc":{"start":48948,"end":49106,"line":{"s":1349,"e":1350,"code":["...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,","so `search` and `getUser` are directly accessible in the body without any import."]},"column":{"s":0,"e":81}},"dim":["","paragraph.267"],"code":"...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import."},{"id":"/root/children/267/children/0","type":"text","loc":{"start":48948,"end":48981,"line":{"s":1349,"e":1349,"code":["...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,"]},"column":{"s":0,"e":33}},"dim":["","paragraph.267","text.0"],"code":"...is compiled to something like "},{"id":"/root/children/267/children/1","type":"inlineCode","loc":{"start":48981,"end":49023,"line":{"s":1349,"e":1349,"code":["...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,"]},"column":{"s":33,"e":75}},"dim":["","paragraph.267","inlineCode.1"],"code":"`AsyncFunction(search, getUser, bodyText)`"},{"id":"/root/children/267/children/2","type":"text","loc":{"start":49023,"end":49028,"line":{"s":1349,"e":1350,"code":["...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,","so `search` and `getUser` are directly accessible in the body without any import."]},"column":{"s":75,"e":3}},"dim":["","paragraph.267","text.2"],"code":",\nso "},{"id":"/root/children/267/children/3","type":"inlineCode","loc":{"start":49028,"end":49036,"line":{"s":1350,"e":1350,"code":["so `search` and `getUser` are directly accessible in the body without any import."]},"column":{"s":3,"e":11}},"dim":["","paragraph.267","inlineCode.3"],"code":"`search`"},{"id":"/root/children/267/children/4","type":"text","loc":{"start":49036,"end":49041,"line":{"s":1350,"e":1350,"code":["so `search` and `getUser` are directly accessible in the body without any import."]},"column":{"s":11,"e":16}},"dim":["","paragraph.267","text.4"],"code":" and "},{"id":"/root/children/267/children/5","type":"inlineCode","loc":{"start":49041,"end":49050,"line":{"s":1350,"e":1350,"code":["so `search` and `getUser` are directly accessible in the body without any import."]},"column":{"s":16,"e":25}},"dim":["","paragraph.267","inlineCode.5"],"code":"`getUser`"},{"id":"/root/children/267/children/6","type":"text","loc":{"start":49050,"end":49106,"line":{"s":1350,"e":1350,"code":["so `search` and `getUser` are directly accessible in the body without any import."]},"column":{"s":25,"e":81}},"dim":["","paragraph.267","text.6"],"code":" are directly accessible in the body without any import."},{"id":"/root/children/268","type":"heading","loc":{"start":49108,"end":49124,"line":{"s":1352,"e":1352,"code":["### Naming rules"]},"column":{"s":0,"e":16}},"dim":["","heading.268"],"code":"### Naming rules","symbName":"heading","symbRange":[49126,49514],"symbRangeL":[1352,1360],"outerCode":"\n- Keys must be **valid JS identifiers** (no hyphens, no leading digits)\n- Use **camelCase** — this is idiomatic for JS function names\n- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)\n- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`","outerHtml":"\n<ul><li>Keys must be <b>valid JS identifiers</b> (no hyphens, no leading digits)</li><li>Use <b>camelCase</b> — this is idiomatic for JS function names</li><li>Avoid the `<i>mdt</i>` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)</li><li>Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`</li></ul>"},{"id":"/root/children/268/children/0","type":"text","loc":{"start":49112,"end":49124,"line":{"s":1352,"e":1352,"code":["### Naming rules"]},"column":{"s":4,"e":16}},"dim":["","heading.268","text.0"],"code":"Naming rules"},{"id":"/root/children/269","type":"list","loc":{"start":49126,"end":49514,"line":{"s":1354,"e":1359,"code":["- Keys must be **valid JS identifiers** (no hyphens, no leading digits)","- Use **camelCase** — this is idiomatic for JS function names","- Avoid the `_mdt_` prefix — that's reserved for library-injected names","  (currently only `_mdt_label`)","- Names that collide with JavaScript reserved words (`class`, `return`, `await`)","  will break — if you need one, alias it: `{ searchClass: ..., ... }`"]},"column":{"s":0,"e":69}},"dim":["","list.269"],"code":"- Keys must be **valid JS identifiers** (no hyphens, no leading digits)\n- Use **camelCase** — this is idiomatic for JS function names\n- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)\n- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`","symbName":"list","symbRange":[49516,49729],"symbRangeL":[1354,1366],"outerCode":"- Use **camelCase** — this is idiomatic for JS function names\n- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)\n- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`\n\n### Return protocol\n\nAdapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:","outerHtml":"<ul><li>Use <b>camelCase</b> — this is idiomatic for JS function names</li><li>Avoid the `<i>mdt</i>` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)</li><li>Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`</li></ul>\n\n<h3>Return protocol</h3>\n\n<p>Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:</p>"},{"id":"/root/children/269/children/0","type":"listItem","loc":{"start":49126,"end":49197,"line":{"s":1354,"e":1354,"code":["- Keys must be **valid JS identifiers** (no hyphens, no leading digits)"]},"column":{"s":0,"e":71}},"dim":["","list.269","listItem.0"],"code":"- Keys must be **valid JS identifiers** (no hyphens, no leading digits)"},{"id":"/root/children/269/children/0/children/0","type":"paragraph","loc":{"start":49128,"end":49197,"line":{"s":1354,"e":1354,"code":["- Keys must be **valid JS identifiers** (no hyphens, no leading digits)"]},"column":{"s":2,"e":71}},"dim":["","list.269","listItem.0","paragraph.0"],"code":"Keys must be **valid JS identifiers** (no hyphens, no leading digits)"},{"id":"/root/children/269/children/0/children/0/children/0","type":"text","loc":{"start":49128,"end":49141,"line":{"s":1354,"e":1354,"code":["- Keys must be **valid JS identifiers** (no hyphens, no leading digits)"]},"column":{"s":2,"e":15}},"dim":["","list.269","listItem.0","paragraph.0","text.0"],"code":"Keys must be "},{"id":"/root/children/269/children/0/children/0/children/1","type":"strong","loc":{"start":49141,"end":49165,"line":{"s":1354,"e":1354,"code":["- Keys must be **valid JS identifiers** (no hyphens, no leading digits)"]},"column":{"s":15,"e":39}},"dim":["","list.269","listItem.0","paragraph.0","strong.1"],"code":"**valid JS identifiers**"},{"id":"/root/children/269/children/0/children/0/children/1/children/0","type":"text","loc":{"start":49143,"end":49163,"line":{"s":1354,"e":1354,"code":["- Keys must be **valid JS identifiers** (no hyphens, no leading digits)"]},"column":{"s":17,"e":37}},"dim":["","list.269","listItem.0","paragraph.0","strong.1","text.0"],"code":"valid JS identifiers"},{"id":"/root/children/269/children/0/children/0/children/2","type":"text","loc":{"start":49165,"end":49197,"line":{"s":1354,"e":1354,"code":["- Keys must be **valid JS identifiers** (no hyphens, no leading digits)"]},"column":{"s":39,"e":71}},"dim":["","list.269","listItem.0","paragraph.0","text.2"],"code":" (no hyphens, no leading digits)"},{"id":"/root/children/269/children/1","type":"listItem","loc":{"start":49198,"end":49259,"line":{"s":1355,"e":1355,"code":["- Use **camelCase** — this is idiomatic for JS function names"]},"column":{"s":0,"e":61}},"dim":["","list.269","listItem.1"],"code":"- Use **camelCase** — this is idiomatic for JS function names"},{"id":"/root/children/269/children/1/children/0","type":"paragraph","loc":{"start":49200,"end":49259,"line":{"s":1355,"e":1355,"code":["- Use **camelCase** — this is idiomatic for JS function names"]},"column":{"s":2,"e":61}},"dim":["","list.269","listItem.1","paragraph.0"],"code":"Use **camelCase** — this is idiomatic for JS function names"},{"id":"/root/children/269/children/1/children/0/children/0","type":"text","loc":{"start":49200,"end":49204,"line":{"s":1355,"e":1355,"code":["- Use **camelCase** — this is idiomatic for JS function names"]},"column":{"s":2,"e":6}},"dim":["","list.269","listItem.1","paragraph.0","text.0"],"code":"Use "},{"id":"/root/children/269/children/1/children/0/children/1","type":"strong","loc":{"start":49204,"end":49217,"line":{"s":1355,"e":1355,"code":["- Use **camelCase** — this is idiomatic for JS function names"]},"column":{"s":6,"e":19}},"dim":["","list.269","listItem.1","paragraph.0","strong.1"],"code":"**camelCase**"},{"id":"/root/children/269/children/1/children/0/children/1/children/0","type":"text","loc":{"start":49206,"end":49215,"line":{"s":1355,"e":1355,"code":["- Use **camelCase** — this is idiomatic for JS function names"]},"column":{"s":8,"e":17}},"dim":["","list.269","listItem.1","paragraph.0","strong.1","text.0"],"code":"camelCase"},{"id":"/root/children/269/children/1/children/0/children/2","type":"text","loc":{"start":49217,"end":49259,"line":{"s":1355,"e":1355,"code":["- Use **camelCase** — this is idiomatic for JS function names"]},"column":{"s":19,"e":61}},"dim":["","list.269","listItem.1","paragraph.0","text.2"],"code":" — this is idiomatic for JS function names"},{"id":"/root/children/269/children/2","type":"listItem","loc":{"start":49260,"end":49363,"line":{"s":1356,"e":1357,"code":["- Avoid the `_mdt_` prefix — that's reserved for library-injected names","  (currently only `_mdt_label`)"]},"column":{"s":0,"e":31}},"dim":["","list.269","listItem.2"],"code":"- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)"},{"id":"/root/children/269/children/2/children/0","type":"paragraph","loc":{"start":49262,"end":49363,"line":{"s":1356,"e":1357,"code":["- Avoid the `_mdt_` prefix — that's reserved for library-injected names","  (currently only `_mdt_label`)"]},"column":{"s":2,"e":31}},"dim":["","list.269","listItem.2","paragraph.0"],"code":"Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)"},{"id":"/root/children/269/children/2/children/0/children/0","type":"text","loc":{"start":49262,"end":49272,"line":{"s":1356,"e":1356,"code":["- Avoid the `_mdt_` prefix — that's reserved for library-injected names"]},"column":{"s":2,"e":12}},"dim":["","list.269","listItem.2","paragraph.0","text.0"],"code":"Avoid the "},{"id":"/root/children/269/children/2/children/0/children/1","type":"inlineCode","loc":{"start":49272,"end":49279,"line":{"s":1356,"e":1356,"code":["- Avoid the `_mdt_` prefix — that's reserved for library-injected names"]},"column":{"s":12,"e":19}},"dim":["","list.269","listItem.2","paragraph.0","inlineCode.1"],"code":"`_mdt_`"},{"id":"/root/children/269/children/2/children/0/children/2","type":"text","loc":{"start":49279,"end":49350,"line":{"s":1356,"e":1357,"code":["- Avoid the `_mdt_` prefix — that's reserved for library-injected names","  (currently only `_mdt_label`)"]},"column":{"s":19,"e":18}},"dim":["","list.269","listItem.2","paragraph.0","text.2"],"code":" prefix — that's reserved for library-injected names\n  (currently only "},{"id":"/root/children/269/children/2/children/0/children/3","type":"inlineCode","loc":{"start":49350,"end":49362,"line":{"s":1357,"e":1357,"code":["  (currently only `_mdt_label`)"]},"column":{"s":18,"e":30}},"dim":["","list.269","listItem.2","paragraph.0","inlineCode.3"],"code":"`_mdt_label`"},{"id":"/root/children/269/children/2/children/0/children/4","type":"text","loc":{"start":49362,"end":49363,"line":{"s":1357,"e":1357,"code":["  (currently only `_mdt_label`)"]},"column":{"s":30,"e":31}},"dim":["","list.269","listItem.2","paragraph.0","text.4"],"code":")"},{"id":"/root/children/269/children/3","type":"listItem","loc":{"start":49364,"end":49514,"line":{"s":1358,"e":1359,"code":["- Names that collide with JavaScript reserved words (`class`, `return`, `await`)","  will break — if you need one, alias it: `{ searchClass: ..., ... }`"]},"column":{"s":0,"e":69}},"dim":["","list.269","listItem.3"],"code":"- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`"},{"id":"/root/children/269/children/3/children/0","type":"paragraph","loc":{"start":49366,"end":49514,"line":{"s":1358,"e":1359,"code":["- Names that collide with JavaScript reserved words (`class`, `return`, `await`)","  will break — if you need one, alias it: `{ searchClass: ..., ... }`"]},"column":{"s":2,"e":69}},"dim":["","list.269","listItem.3","paragraph.0"],"code":"Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`"},{"id":"/root/children/269/children/3/children/0/children/0","type":"text","loc":{"start":49366,"end":49417,"line":{"s":1358,"e":1358,"code":["- Names that collide with JavaScript reserved words (`class`, `return`, `await`)"]},"column":{"s":2,"e":53}},"dim":["","list.269","listItem.3","paragraph.0","text.0"],"code":"Names that collide with JavaScript reserved words ("},{"id":"/root/children/269/children/3/children/0/children/1","type":"inlineCode","loc":{"start":49417,"end":49424,"line":{"s":1358,"e":1358,"code":["- Names that collide with JavaScript reserved words (`class`, `return`, `await`)"]},"column":{"s":53,"e":60}},"dim":["","list.269","listItem.3","paragraph.0","inlineCode.1"],"code":"`class`"},{"id":"/root/children/269/children/3/children/0/children/2","type":"text","loc":{"start":49424,"end":49426,"line":{"s":1358,"e":1358,"code":["- Names that collide with JavaScript reserved words (`class`, `return`, `await`)"]},"column":{"s":60,"e":62}},"dim":["","list.269","listItem.3","paragraph.0","text.2"],"code":", "},{"id":"/root/children/269/children/3/children/0/children/3","type":"inlineCode","loc":{"start":49426,"end":49434,"line":{"s":1358,"e":1358,"code":["- Names that collide with JavaScript reserved words (`class`, `return`, `await`)"]},"column":{"s":62,"e":70}},"dim":["","list.269","listItem.3","paragraph.0","inlineCode.3"],"code":"`return`"},{"id":"/root/children/269/children/3/children/0/children/4","type":"text","loc":{"start":49434,"end":49436,"line":{"s":1358,"e":1358,"code":["- Names that collide with JavaScript reserved words (`class`, `return`, `await`)"]},"column":{"s":70,"e":72}},"dim":["","list.269","listItem.3","paragraph.0","text.4"],"code":", "},{"id":"/root/children/269/children/3/children/0/children/5","type":"inlineCode","loc":{"start":49436,"end":49443,"line":{"s":1358,"e":1358,"code":["- Names that collide with JavaScript reserved words (`class`, `return`, `await`)"]},"column":{"s":72,"e":79}},"dim":["","list.269","listItem.3","paragraph.0","inlineCode.5"],"code":"`await`"},{"id":"/root/children/269/children/3/children/0/children/6","type":"text","loc":{"start":49443,"end":49487,"line":{"s":1358,"e":1359,"code":["- Names that collide with JavaScript reserved words (`class`, `return`, `await`)","  will break — if you need one, alias it: `{ searchClass: ..., ... }`"]},"column":{"s":79,"e":42}},"dim":["","list.269","listItem.3","paragraph.0","text.6"],"code":")\n  will break — if you need one, alias it: "},{"id":"/root/children/269/children/3/children/0/children/7","type":"inlineCode","loc":{"start":49487,"end":49514,"line":{"s":1359,"e":1359,"code":["  will break — if you need one, alias it: `{ searchClass: ..., ... }`"]},"column":{"s":42,"e":69}},"dim":["","list.269","listItem.3","paragraph.0","inlineCode.7"],"code":"`{ searchClass: ..., ... }`"},{"id":"/root/children/270","type":"heading","loc":{"start":49516,"end":49535,"line":{"s":1361,"e":1361,"code":["### Return protocol"]},"column":{"s":0,"e":19}},"dim":["","heading.270"],"code":"### Return protocol","symbName":"heading","symbRange":[49537,50051],"symbRangeL":[1361,1373],"outerCode":"\nAdapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:\n\n- `return insert(value)` — the extruction produces output\n- `return undefined` or no return — extruction stays transparent\n- `throw error` — propagates to the consumer (or caught by `onExtructionError`)\n\nThis means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.","outerHtml":"\n<p>Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:</p>\n\n<ul><li>`return insert(value)` — the extruction produces output</li><li>`return undefined` or no return — extruction stays transparent</li><li>`throw error` — propagates to the consumer (or caught by `onExtructionError`)</li></ul>\n\n<p>This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.</p>"},{"id":"/root/children/270/children/0","type":"text","loc":{"start":49520,"end":49535,"line":{"s":1361,"e":1361,"code":["### Return protocol"]},"column":{"s":4,"e":19}},"dim":["","heading.270","text.0"],"code":"Return protocol"},{"id":"/root/children/271","type":"paragraph","loc":{"start":49537,"end":49729,"line":{"s":1363,"e":1365,"code":["Adapters can return anything — there's no adapter-specific protocol.","The extruction body is responsible for handling the return value and deciding","what to do with it via the `insert` protocol:"]},"column":{"s":0,"e":45}},"dim":["","paragraph.271"],"code":"Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:"},{"id":"/root/children/271/children/0","type":"text","loc":{"start":49537,"end":49711,"line":{"s":1363,"e":1365,"code":["Adapters can return anything — there's no adapter-specific protocol.","The extruction body is responsible for handling the return value and deciding","what to do with it via the `insert` protocol:"]},"column":{"s":0,"e":27}},"dim":["","paragraph.271","text.0"],"code":"Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the "},{"id":"/root/children/271/children/1","type":"inlineCode","loc":{"start":49711,"end":49719,"line":{"s":1365,"e":1365,"code":["what to do with it via the `insert` protocol:"]},"column":{"s":27,"e":35}},"dim":["","paragraph.271","inlineCode.1"],"code":"`insert`"},{"id":"/root/children/271/children/2","type":"text","loc":{"start":49719,"end":49729,"line":{"s":1365,"e":1365,"code":["what to do with it via the `insert` protocol:"]},"column":{"s":35,"e":45}},"dim":["","paragraph.271","text.2"],"code":" protocol:"},{"id":"/root/children/272","type":"list","loc":{"start":49731,"end":49933,"line":{"s":1367,"e":1369,"code":["- `return insert(value)` — the extruction produces output","- `return undefined` or no return — extruction stays transparent","- `throw error` — propagates to the consumer (or caught by `onExtructionError`)"]},"column":{"s":0,"e":79}},"dim":["","list.272"],"code":"- `return insert(value)` — the extruction produces output\n- `return undefined` or no return — extruction stays transparent\n- `throw error` — propagates to the consumer (or caught by `onExtructionError`)","symbName":"list","symbRange":[49935,50076],"symbRangeL":[1367,1375],"outerCode":"- `return undefined` or no return — extruction stays transparent\n- `throw error` — propagates to the consumer (or caught by `onExtructionError`)\n\nThis means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.\n\n### Adapter conventions","outerHtml":"<ul><li>`return undefined` or no return — extruction stays transparent</li><li>`throw error` — propagates to the consumer (or caught by `onExtructionError`)</li></ul>\n\n<p>This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.</p>\n\n<h3>Adapter conventions</h3>"},{"id":"/root/children/272/children/0","type":"listItem","loc":{"start":49731,"end":49788,"line":{"s":1367,"e":1367,"code":["- `return insert(value)` — the extruction produces output"]},"column":{"s":0,"e":57}},"dim":["","list.272","listItem.0"],"code":"- `return insert(value)` — the extruction produces output"},{"id":"/root/children/272/children/0/children/0","type":"paragraph","loc":{"start":49733,"end":49788,"line":{"s":1367,"e":1367,"code":["- `return insert(value)` — the extruction produces output"]},"column":{"s":2,"e":57}},"dim":["","list.272","listItem.0","paragraph.0"],"code":"`return insert(value)` — the extruction produces output"},{"id":"/root/children/272/children/0/children/0/children/0","type":"inlineCode","loc":{"start":49733,"end":49755,"line":{"s":1367,"e":1367,"code":["- `return insert(value)` — the extruction produces output"]},"column":{"s":2,"e":24}},"dim":["","list.272","listItem.0","paragraph.0","inlineCode.0"],"code":"`return insert(value)`"},{"id":"/root/children/272/children/0/children/0/children/1","type":"text","loc":{"start":49755,"end":49788,"line":{"s":1367,"e":1367,"code":["- `return insert(value)` — the extruction produces output"]},"column":{"s":24,"e":57}},"dim":["","list.272","listItem.0","paragraph.0","text.1"],"code":" — the extruction produces output"},{"id":"/root/children/272/children/1","type":"listItem","loc":{"start":49789,"end":49853,"line":{"s":1368,"e":1368,"code":["- `return undefined` or no return — extruction stays transparent"]},"column":{"s":0,"e":64}},"dim":["","list.272","listItem.1"],"code":"- `return undefined` or no return — extruction stays transparent"},{"id":"/root/children/272/children/1/children/0","type":"paragraph","loc":{"start":49791,"end":49853,"line":{"s":1368,"e":1368,"code":["- `return undefined` or no return — extruction stays transparent"]},"column":{"s":2,"e":64}},"dim":["","list.272","listItem.1","paragraph.0"],"code":"`return undefined` or no return — extruction stays transparent"},{"id":"/root/children/272/children/1/children/0/children/0","type":"inlineCode","loc":{"start":49791,"end":49809,"line":{"s":1368,"e":1368,"code":["- `return undefined` or no return — extruction stays transparent"]},"column":{"s":2,"e":20}},"dim":["","list.272","listItem.1","paragraph.0","inlineCode.0"],"code":"`return undefined`"},{"id":"/root/children/272/children/1/children/0/children/1","type":"text","loc":{"start":49809,"end":49853,"line":{"s":1368,"e":1368,"code":["- `return undefined` or no return — extruction stays transparent"]},"column":{"s":20,"e":64}},"dim":["","list.272","listItem.1","paragraph.0","text.1"],"code":" or no return — extruction stays transparent"},{"id":"/root/children/272/children/2","type":"listItem","loc":{"start":49854,"end":49933,"line":{"s":1369,"e":1369,"code":["- `throw error` — propagates to the consumer (or caught by `onExtructionError`)"]},"column":{"s":0,"e":79}},"dim":["","list.272","listItem.2"],"code":"- `throw error` — propagates to the consumer (or caught by `onExtructionError`)"},{"id":"/root/children/272/children/2/children/0","type":"paragraph","loc":{"start":49856,"end":49933,"line":{"s":1369,"e":1369,"code":["- `throw error` — propagates to the consumer (or caught by `onExtructionError`)"]},"column":{"s":2,"e":79}},"dim":["","list.272","listItem.2","paragraph.0"],"code":"`throw error` — propagates to the consumer (or caught by `onExtructionError`)"},{"id":"/root/children/272/children/2/children/0/children/0","type":"inlineCode","loc":{"start":49856,"end":49869,"line":{"s":1369,"e":1369,"code":["- `throw error` — propagates to the consumer (or caught by `onExtructionError`)"]},"column":{"s":2,"e":15}},"dim":["","list.272","listItem.2","paragraph.0","inlineCode.0"],"code":"`throw error`"},{"id":"/root/children/272/children/2/children/0/children/1","type":"text","loc":{"start":49869,"end":49913,"line":{"s":1369,"e":1369,"code":["- `throw error` — propagates to the consumer (or caught by `onExtructionError`)"]},"column":{"s":15,"e":59}},"dim":["","list.272","listItem.2","paragraph.0","text.1"],"code":" — propagates to the consumer (or caught by "},{"id":"/root/children/272/children/2/children/0/children/2","type":"inlineCode","loc":{"start":49913,"end":49932,"line":{"s":1369,"e":1369,"code":["- `throw error` — propagates to the consumer (or caught by `onExtructionError`)"]},"column":{"s":59,"e":78}},"dim":["","list.272","listItem.2","paragraph.0","inlineCode.2"],"code":"`onExtructionError`"},{"id":"/root/children/272/children/2/children/0/children/3","type":"text","loc":{"start":49932,"end":49933,"line":{"s":1369,"e":1369,"code":["- `throw error` — propagates to the consumer (or caught by `onExtructionError`)"]},"column":{"s":78,"e":79}},"dim":["","list.272","listItem.2","paragraph.0","text.3"],"code":")"},{"id":"/root/children/273","type":"paragraph","loc":{"start":49935,"end":50051,"line":{"s":1371,"e":1372,"code":["This means adapters can return raw data (arrays, objects, strings) and the","extruction body formats it into markdown."]},"column":{"s":0,"e":41}},"dim":["","paragraph.273"],"code":"This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown."},{"id":"/root/children/273/children/0","type":"text","loc":{"start":49935,"end":50051,"line":{"s":1371,"e":1372,"code":["This means adapters can return raw data (arrays, objects, strings) and the","extruction body formats it into markdown."]},"column":{"s":0,"e":41}},"dim":["","paragraph.273","text.0"],"code":"This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown."},{"id":"/root/children/274","type":"heading","loc":{"start":50053,"end":50076,"line":{"s":1374,"e":1374,"code":["### Adapter conventions"]},"column":{"s":0,"e":23}},"dim":["","heading.274"],"code":"### Adapter conventions","symbName":"heading","symbRange":[50078,50879],"symbRangeL":[1374,1399],"outerCode":"\n1. **Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.\n\n2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.\n\n3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:\n\n\n```\n   ## ${search mdd}\n\n   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```\n\nThis is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.","outerHtml":"\n<ol><li><b>Async by convention</b> — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.</li></ol>\n\n<ol><li><b>Error handling</b> — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.</li></ol>\n\n<ol><li><b>`_mdt_label`</b> — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:</li></ol>\n\n\n<p>```</p><h2>${search mdd}</h2>\n\n<p>   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```</p>\n\n<p>This is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.</p>"},{"id":"/root/children/274/children/0","type":"text","loc":{"start":50057,"end":50076,"line":{"s":1374,"e":1374,"code":["### Adapter conventions"]},"column":{"s":4,"e":23}},"dim":["","heading.274","text.0"],"code":"Adapter conventions"},{"id":"/root/children/275","type":"list","loc":{"start":50078,"end":50659,"line":{"s":1376,"e":1386,"code":["1. **Async by convention** — make adapters `async` even if they're sync.","   The extruction body uses `await` consistently, and an `async` adapter that","   happens to resolve synchronously is cheaper than a sync adapter that the","   body wraps in `Promise.resolve()`.","","2. **Error handling** — let errors propagate. The extruction body handles them","   if needed, or `onExtructionError` catches globally.","   Don't silently swallow errors in the adapter.","","3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.","   Adapters can receive it explicitly from the body:"]},"column":{"s":0,"e":52}},"dim":["","list.275"],"code":"1. **Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.\n\n2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.\n\n3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:","symbName":"list","symbRange":[50662,56384],"symbRangeL":[1376,1653],"outerCode":"   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.\n\n2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.\n\n3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:\n\n\n```\n   ## ${search mdd}\n\n   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```\n\nThis is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.\n\n## Example adapters\n\n### 1. Simple lookup (sync)\n\n```js\n\nconst repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};\n\nconst doc = runner({ repoInfo }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${repo info}\n\n\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`**${r.stars}** stars — ${r.description}\\` )\n\\`\\`\\`\n\n\n```\n\n### 2. Search adapter\n\nAlready documented in [Search Adapter](#search-adapter). The pattern:\n\n```js\n\nimport { search } from \"./mdt/search-adapter.js\";\n\nconst doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);\n\n\n```\n\n```\n\n## ${results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- [\\${i.name}](${i.uri})\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\nThe key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.\n\n### 3. HTTP fetch\n\n```js\n\nconst fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};\n\nconst doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);\n\n\n```\n\n```\n\n## ${github stats}\n\n\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`\n\n\n```\n\nThe adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.\n\n### 4. Database query\n\n```js\n\nconst queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};\n\nconst doc = runner({ queryDb }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${active users}\n\n\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\n### 5. State access\n\nWhen the runner context includes the app's state object, extructions can read\nfrom it directly:\n\n```js\n\nconst doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${welcome}\n\n\\`\\`\\`javascript\nreturn insert( \\`Hello **\\${currentUser}**, you have \\${STATE.notifications.length} notifications\\` )\n\\`\\`\\`\n\n\n```\n\nThis is how the app passes its reactive state into extruction bodies.\n\n### 6. Composition — multiple adapters\n\nAdapters compose naturally since they're just JS functions:\n\n```js\n\nconst doc = runner({ repoInfo, fetchJson, currentUser }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${dashboard}\n\n\\`\\`\\`javascript\nconst user = currentUser\nconst repos = await fetchJson(\\`https://api.github.com/users/\\${user}/repos\\`)\nconst summary = repos.map(r => \\`- \\${r.name}: \\${repoInfo[r.name]?.description || \"unknown\"}\\`).join(\"\\n\")\nreturn insert( \\`### \\${user}'s repos\\n\\n\\${summary}\\` )\n\\`\\`\\`\n\n\n```\n\nHere `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is\na plain string — all coexist as named parameters.\n\n### 7. Using `_mdt_label` to drive adapters\n\nThe label (text between `${}`) is injected as `_mdt_label` automatically.\nThis lets a single adapter serve multiple extruction variants:\n\n```\n\n## ${fetch todos}\n\n\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/todos\"))\n\\`\\`\\`\n\n## ${fetch users}\n\n\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/users\") )\n\\`\\`\\`\n\n\n```\n\nWithout hardcoding the path in each body — although in this case you'd still\nneed to map the label to the path. A more practical use:\n\n```\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n\n## ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.name).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\nThe same `search` adapter is called with different labels.\n\n### Key constraints\n\n| Constraint                                         | Why                                                         |\n| -------------------------------------------------- | ----------------------------------------------------------- |\n| Adapter names must be valid JS identifiers         | They become `AsyncFunction` parameter names                 |\n| Don't use `_mdt_` prefix                           | Reserved for library-injected context keys                  |\n| Adapters are evaluated fresh on each `evalFn` call | No caching — each expansion re-evaluates                    |\n| Return `{ insert }` to produce output              | Any other return keeps the extruction transparent           |\n| Context is spread, not just the adapter            | All context keys are available — plan namespace accordingly |\n\n### 6. E2E tests\n\nTest the full player-paper.js integration: `.mdt` file fetch → compile →\nrun with evalBody + adapters → rebuild clean md → render.\n\n---\n\n## Conversion tree — transclusion provenance\n\nWhen an mdt document transcludes content from source fragments (via extructions), the produced fragments have virtual trail positions in the generated document. To resolve these back to the real source fragments, the mdt runner produces a **conversion tree** alongside the generated text.\n\n### sourceFragment field\n\nEach `Fragment` now carries an optional `sourceFragment`:\n\n```js\n\n{\ntrail: \"a/x\",\nheading: \"## <!-- ... -->\",\nbody: \"hello\",\nsourceFragment: null | { fn: \"paper/real.mdd\", refId: \"paper/real.mdd::real/heading\" }\n}\n\n\n```","outerHtml":"<p>   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.</p>\n\n<ol><li><b>Error handling</b> — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.</li></ol>\n\n<ol><li><b>`_mdt_label`</b> — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:</li></ol>\n\n\n<p>```</p><h2>${search mdd}</h2>\n\n<p>   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```</p>\n\n<p>This is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.</p>\n\n<h2>Example adapters</h2>\n\n<h3>1. Simple lookup (sync)</h3>\n\n<p>```js</p>\n\n<p>const repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};</p>\n\n<p>const doc = runner({ repoInfo }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${repo info}</h2>\n\n<p>\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`<b>${r.stars}</b> stars — ${r.description}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>2. Search adapter</h3>\n\n<p>Already documented in <a href=\"#search-adapter\">Search Adapter</a>. The pattern:</p>\n\n<p>```js</p>\n\n<p>import { search } from \"./mdt/search-adapter.js\";</p>\n\n<p>const doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- <a href=\"${i.uri}\">\\${i.name}</a>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.</p>\n\n<h3>3. HTTP fetch</h3>\n\n<p>```js</p>\n\n<p>const fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};</p>\n\n<p>const doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${github stats}</h2>\n\n<p>\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.</p>\n\n<h3>4. Database query</h3>\n\n<p>```js</p>\n\n<p>const queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};</p>\n\n<p>const doc = runner({ queryDb }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${active users}</h2>\n\n<p>\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>5. State access</h3>\n\n<p>When the runner context includes the app's state object, extructions can read\nfrom it directly:</p>\n\n<p>```js</p>\n\n<p>const doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${welcome}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Hello <b>\\${currentUser}</b>, you have \\${STATE.notifications.length} notifications\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>This is how the app passes its reactive state into extruction bodies.</p>\n\n<h3>6. Composition — multiple adapters</h3>\n\n<p>Adapters compose naturally since they're just JS functions:</p>\n\n<p>```js</p>\n\n<p>const doc = runner({ repoInfo, fetchJson, currentUser }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${dashboard}</h2>\n\n<p>\\`\\`\\`javascript\nconst user = currentUser\nconst repos = await fetchJson(\\`https://api.github.com/users/\\${user}/repos\\`)\nconst summary = repos.map(r => \\`- \\${r.name}: \\${repoInfo[r.name]?.description || \"unknown\"}\\`).join(\"\\n\")\nreturn insert( \\`### \\${user}'s repos\\n\\n\\${summary}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>Here `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is\na plain string — all coexist as named parameters.</p>\n\n<h3>7. Using `_mdt_label` to drive adapters</h3>\n\n<p>The label (text between `${}`) is injected as `_mdt_label` automatically.\nThis lets a single adapter serve multiple extruction variants:</p>\n\n<p>```</p>\n\n<h2>${fetch todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/todos\"))\n\\`\\`\\`</p>\n\n<h2>${fetch users}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/users\") )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>Without hardcoding the path in each body — although in this case you'd still\nneed to map the label to the path. A more practical use:</p>\n\n<p>```</p>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>${search js}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.name).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The same `search` adapter is called with different labels.</p>\n\n<h3>Key constraints</h3>\n\n<p>| Constraint                                         | Why                                                         |\n| -------------------------------------------------- | ----------------------------------------------------------- |\n| Adapter names must be valid JS identifiers         | They become `AsyncFunction` parameter names                 |\n| Don't use `<i>mdt</i>` prefix                           | Reserved for library-injected context keys                  |\n| Adapters are evaluated fresh on each `evalFn` call | No caching — each expansion re-evaluates                    |\n| Return `{ insert }` to produce output              | Any other return keeps the extruction transparent           |\n| Context is spread, not just the adapter            | All context keys are available — plan namespace accordingly |</p>\n\n<h3>6. E2E tests</h3>\n\n<p>Test the full player-paper.js integration: `.mdt` file fetch → compile →\nrun with evalBody + adapters → rebuild clean md → render.</p>\n\n<hr/>\n\n<h2>Conversion tree — transclusion provenance</h2>\n\n<p>When an mdt document transcludes content from source fragments (via extructions), the produced fragments have virtual trail positions in the generated document. To resolve these back to the real source fragments, the mdt runner produces a <b>conversion tree</b> alongside the generated text.</p>\n\n<h3>sourceFragment field</h3>\n\n<p>Each `Fragment` now carries an optional `sourceFragment`:</p>\n\n<p>```js</p>\n\n<p>{\ntrail: \"a/x\",\nheading: \"## <!-- ... -->\",\nbody: \"hello\",\nsourceFragment: null | { fn: \"paper/real.mdd\", refId: \"paper/real.mdd::real/heading\" }\n}</p>\n\n\n<p>```</p>"},{"id":"/root/children/275/children/0","type":"listItem","loc":{"start":50078,"end":50342,"line":{"s":1376,"e":1379,"code":["1. **Async by convention** — make adapters `async` even if they're sync.","   The extruction body uses `await` consistently, and an `async` adapter that","   happens to resolve synchronously is cheaper than a sync adapter that the","   body wraps in `Promise.resolve()`."]},"column":{"s":0,"e":37}},"dim":["","list.275","listItem.0"],"code":"1. **Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`."},{"id":"/root/children/275/children/0/children/0","type":"paragraph","loc":{"start":50081,"end":50342,"line":{"s":1376,"e":1379,"code":["1. **Async by convention** — make adapters `async` even if they're sync.","   The extruction body uses `await` consistently, and an `async` adapter that","   happens to resolve synchronously is cheaper than a sync adapter that the","   body wraps in `Promise.resolve()`."]},"column":{"s":3,"e":37}},"dim":["","list.275","listItem.0","paragraph.0"],"code":"**Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`."},{"id":"/root/children/275/children/0/children/0/children/0","type":"strong","loc":{"start":50081,"end":50104,"line":{"s":1376,"e":1376,"code":["1. **Async by convention** — make adapters `async` even if they're sync."]},"column":{"s":3,"e":26}},"dim":["","list.275","listItem.0","paragraph.0","strong.0"],"code":"**Async by convention**"},{"id":"/root/children/275/children/0/children/0/children/0/children/0","type":"text","loc":{"start":50083,"end":50102,"line":{"s":1376,"e":1376,"code":["1. **Async by convention** — make adapters `async` even if they're sync."]},"column":{"s":5,"e":24}},"dim":["","list.275","listItem.0","paragraph.0","strong.0","text.0"],"code":"Async by convention"},{"id":"/root/children/275/children/0/children/0/children/1","type":"text","loc":{"start":50104,"end":50121,"line":{"s":1376,"e":1376,"code":["1. **Async by convention** — make adapters `async` even if they're sync."]},"column":{"s":26,"e":43}},"dim":["","list.275","listItem.0","paragraph.0","text.1"],"code":" — make adapters "},{"id":"/root/children/275/children/0/children/0/children/2","type":"inlineCode","loc":{"start":50121,"end":50128,"line":{"s":1376,"e":1376,"code":["1. **Async by convention** — make adapters `async` even if they're sync."]},"column":{"s":43,"e":50}},"dim":["","list.275","listItem.0","paragraph.0","inlineCode.2"],"code":"`async`"},{"id":"/root/children/275/children/0/children/0/children/3","type":"text","loc":{"start":50128,"end":50179,"line":{"s":1376,"e":1377,"code":["1. **Async by convention** — make adapters `async` even if they're sync.","   The extruction body uses `await` consistently, and an `async` adapter that"]},"column":{"s":50,"e":28}},"dim":["","list.275","listItem.0","paragraph.0","text.3"],"code":" even if they're sync.\n   The extruction body uses "},{"id":"/root/children/275/children/0/children/0/children/4","type":"inlineCode","loc":{"start":50179,"end":50186,"line":{"s":1377,"e":1377,"code":["   The extruction body uses `await` consistently, and an `async` adapter that"]},"column":{"s":28,"e":35}},"dim":["","list.275","listItem.0","paragraph.0","inlineCode.4"],"code":"`await`"},{"id":"/root/children/275/children/0/children/0/children/5","type":"text","loc":{"start":50186,"end":50208,"line":{"s":1377,"e":1377,"code":["   The extruction body uses `await` consistently, and an `async` adapter that"]},"column":{"s":35,"e":57}},"dim":["","list.275","listItem.0","paragraph.0","text.5"],"code":" consistently, and an "},{"id":"/root/children/275/children/0/children/0/children/6","type":"inlineCode","loc":{"start":50208,"end":50215,"line":{"s":1377,"e":1377,"code":["   The extruction body uses `await` consistently, and an `async` adapter that"]},"column":{"s":57,"e":64}},"dim":["","list.275","listItem.0","paragraph.0","inlineCode.6"],"code":"`async`"},{"id":"/root/children/275/children/0/children/0/children/7","type":"text","loc":{"start":50215,"end":50322,"line":{"s":1377,"e":1379,"code":["   The extruction body uses `await` consistently, and an `async` adapter that","   happens to resolve synchronously is cheaper than a sync adapter that the","   body wraps in `Promise.resolve()`."]},"column":{"s":64,"e":17}},"dim":["","list.275","listItem.0","paragraph.0","text.7"],"code":" adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in "},{"id":"/root/children/275/children/0/children/0/children/8","type":"inlineCode","loc":{"start":50322,"end":50341,"line":{"s":1379,"e":1379,"code":["   body wraps in `Promise.resolve()`."]},"column":{"s":17,"e":36}},"dim":["","list.275","listItem.0","paragraph.0","inlineCode.8"],"code":"`Promise.resolve()`"},{"id":"/root/children/275/children/0/children/0/children/9","type":"text","loc":{"start":50341,"end":50342,"line":{"s":1379,"e":1379,"code":["   body wraps in `Promise.resolve()`."]},"column":{"s":36,"e":37}},"dim":["","list.275","listItem.0","paragraph.0","text.9"],"code":"."},{"id":"/root/children/275/children/1","type":"listItem","loc":{"start":50344,"end":50526,"line":{"s":1381,"e":1383,"code":["2. **Error handling** — let errors propagate. The extruction body handles them","   if needed, or `onExtructionError` catches globally.","   Don't silently swallow errors in the adapter."]},"column":{"s":0,"e":48}},"dim":["","list.275","listItem.1"],"code":"2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter."},{"id":"/root/children/275/children/1/children/0","type":"paragraph","loc":{"start":50347,"end":50526,"line":{"s":1381,"e":1383,"code":["2. **Error handling** — let errors propagate. The extruction body handles them","   if needed, or `onExtructionError` catches globally.","   Don't silently swallow errors in the adapter."]},"column":{"s":3,"e":48}},"dim":["","list.275","listItem.1","paragraph.0"],"code":"**Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter."},{"id":"/root/children/275/children/1/children/0/children/0","type":"strong","loc":{"start":50347,"end":50365,"line":{"s":1381,"e":1381,"code":["2. **Error handling** — let errors propagate. The extruction body handles them"]},"column":{"s":3,"e":21}},"dim":["","list.275","listItem.1","paragraph.0","strong.0"],"code":"**Error handling**"},{"id":"/root/children/275/children/1/children/0/children/0/children/0","type":"text","loc":{"start":50349,"end":50363,"line":{"s":1381,"e":1381,"code":["2. **Error handling** — let errors propagate. The extruction body handles them"]},"column":{"s":5,"e":19}},"dim":["","list.275","listItem.1","paragraph.0","strong.0","text.0"],"code":"Error handling"},{"id":"/root/children/275/children/1/children/0/children/1","type":"text","loc":{"start":50365,"end":50440,"line":{"s":1381,"e":1382,"code":["2. **Error handling** — let errors propagate. The extruction body handles them","   if needed, or `onExtructionError` catches globally."]},"column":{"s":21,"e":17}},"dim":["","list.275","listItem.1","paragraph.0","text.1"],"code":" — let errors propagate. The extruction body handles them\n   if needed, or "},{"id":"/root/children/275/children/1/children/0/children/2","type":"inlineCode","loc":{"start":50440,"end":50459,"line":{"s":1382,"e":1382,"code":["   if needed, or `onExtructionError` catches globally."]},"column":{"s":17,"e":36}},"dim":["","list.275","listItem.1","paragraph.0","inlineCode.2"],"code":"`onExtructionError`"},{"id":"/root/children/275/children/1/children/0/children/3","type":"text","loc":{"start":50459,"end":50526,"line":{"s":1382,"e":1383,"code":["   if needed, or `onExtructionError` catches globally.","   Don't silently swallow errors in the adapter."]},"column":{"s":36,"e":48}},"dim":["","list.275","listItem.1","paragraph.0","text.3"],"code":" catches globally.\n   Don't silently swallow errors in the adapter."},{"id":"/root/children/275/children/2","type":"listItem","loc":{"start":50528,"end":50659,"line":{"s":1385,"e":1386,"code":["3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.","   Adapters can receive it explicitly from the body:"]},"column":{"s":0,"e":52}},"dim":["","list.275","listItem.2"],"code":"3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:"},{"id":"/root/children/275/children/2/children/0","type":"paragraph","loc":{"start":50531,"end":50659,"line":{"s":1385,"e":1386,"code":["3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.","   Adapters can receive it explicitly from the body:"]},"column":{"s":3,"e":52}},"dim":["","list.275","listItem.2","paragraph.0"],"code":"**`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:"},{"id":"/root/children/275/children/2/children/0/children/0","type":"strong","loc":{"start":50531,"end":50547,"line":{"s":1385,"e":1385,"code":["3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`."]},"column":{"s":3,"e":19}},"dim":["","list.275","listItem.2","paragraph.0","strong.0"],"code":"**`_mdt_label`**"},{"id":"/root/children/275/children/2/children/0/children/0/children/0","type":"inlineCode","loc":{"start":50533,"end":50545,"line":{"s":1385,"e":1385,"code":["3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`."]},"column":{"s":5,"e":17}},"dim":["","list.275","listItem.2","paragraph.0","strong.0","inlineCode.0"],"code":"`_mdt_label`"},{"id":"/root/children/275/children/2/children/0/children/1","type":"text","loc":{"start":50547,"end":50593,"line":{"s":1385,"e":1385,"code":["3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`."]},"column":{"s":19,"e":65}},"dim":["","list.275","listItem.2","paragraph.0","text.1"],"code":" — each extruction has its label available as "},{"id":"/root/children/275/children/2/children/0/children/2","type":"inlineCode","loc":{"start":50593,"end":50605,"line":{"s":1385,"e":1385,"code":["3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`."]},"column":{"s":65,"e":77}},"dim":["","list.275","listItem.2","paragraph.0","inlineCode.2"],"code":"`_mdt_label`"},{"id":"/root/children/275/children/2/children/0/children/3","type":"text","loc":{"start":50605,"end":50659,"line":{"s":1385,"e":1386,"code":["3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.","   Adapters can receive it explicitly from the body:"]},"column":{"s":77,"e":52}},"dim":["","list.275","listItem.2","paragraph.0","text.3"],"code":".\n   Adapters can receive it explicitly from the body:"},{"id":"/root/children/276","type":"code","loc":{"start":50662,"end":50767,"line":{"s":1389,"e":1395,"code":["```","   ## ${search mdd}","","   \\`\\`\\`javascript","   return insert( await search(_mdt_label))","   \\`\\`\\`","   ```"]},"column":{"s":0,"e":6}},"dim":["","code.276"],"code":"```\n   ## ${search mdd}\n\n   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```","symbName":"code","symbRange":[50769,50929],"symbRangeL":[null,1403],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```\n\n### How it works\n\nGiven this setup:\n\n```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```\n\nAn extruction body like:\n\n\n```\n## ${find stuff}\n\n\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```\n\n...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.\n\n### Naming rules\n\n- Keys must be **valid JS identifiers** (no hyphens, no leading digits)\n- Use **camelCase** — this is idiomatic for JS function names\n- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)\n- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`\n\n### Return protocol\n\nAdapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:\n\n- `return insert(value)` — the extruction produces output\n- `return undefined` or no return — extruction stays transparent\n- `throw error` — propagates to the consumer (or caught by `onExtructionError`)\n\nThis means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.\n\n### Adapter conventions\n\n1. **Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.\n\n2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.\n\n3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:\n\n\n```\n   ## ${search mdd}\n\n   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```\n\nThis is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.\n\n## Example adapters\n\n### 1. Simple lookup (sync)","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>\n\n<h3>How it works</h3>\n\n<p>Given this setup:</p>\n\n<p>```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```</p>\n\n<p>An extruction body like:</p>\n\n\n<p>```</p><h2>${find stuff}</h2>\n\n<p>\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<p>...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.</p>\n\n<h3>Naming rules</h3>\n\n<ul><li>Keys must be <b>valid JS identifiers</b> (no hyphens, no leading digits)</li><li>Use <b>camelCase</b> — this is idiomatic for JS function names</li><li>Avoid the `<i>mdt</i>` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)</li><li>Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`</li></ul>\n\n<h3>Return protocol</h3>\n\n<p>Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:</p>\n\n<ul><li>`return insert(value)` — the extruction produces output</li><li>`return undefined` or no return — extruction stays transparent</li><li>`throw error` — propagates to the consumer (or caught by `onExtructionError`)</li></ul>\n\n<p>This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.</p>\n\n<h3>Adapter conventions</h3>\n\n<ol><li><b>Async by convention</b> — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.</li></ol>\n\n<ol><li><b>Error handling</b> — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.</li></ol>\n\n<ol><li><b>`_mdt_label`</b> — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:</li></ol>\n\n\n<p>```</p><h2>${search mdd}</h2>\n\n<p>   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```</p>\n\n<p>This is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.</p>\n\n<h2>Example adapters</h2>\n\n<h3>1. Simple lookup (sync)</h3>"},{"id":"/root/children/277","type":"paragraph","loc":{"start":50769,"end":50879,"line":{"s":1397,"e":1398,"code":["This is how the same adapter can be driven by different extruction labels","without hardcoding the query string."]},"column":{"s":0,"e":36}},"dim":["","paragraph.277"],"code":"This is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string."},{"id":"/root/children/277/children/0","type":"text","loc":{"start":50769,"end":50879,"line":{"s":1397,"e":1398,"code":["This is how the same adapter can be driven by different extruction labels","without hardcoding the query string."]},"column":{"s":0,"e":36}},"dim":["","paragraph.277","text.0"],"code":"This is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string."},{"id":"/root/children/278","type":"heading","loc":{"start":50881,"end":50900,"line":{"s":1400,"e":1400,"code":["## Example adapters"]},"column":{"s":0,"e":19}},"dim":["","heading.278"],"code":"## Example adapters","symbName":"heading","symbRange":[50902,58640],"symbRangeL":[1400,1401],"outerCode":"","outerHtml":""},{"id":"/root/children/278/children/0","type":"text","loc":{"start":50884,"end":50900,"line":{"s":1400,"e":1400,"code":["## Example adapters"]},"column":{"s":3,"e":19}},"dim":["","heading.278","text.0"],"code":"Example adapters"},{"id":"/root/children/279","type":"heading","loc":{"start":50902,"end":50929,"line":{"s":1402,"e":1402,"code":["### 1. Simple lookup (sync)"]},"column":{"s":0,"e":27}},"dim":["","heading.279"],"code":"### 1. Simple lookup (sync)","symbName":"heading","symbRange":[50931,51287],"symbRangeL":[1402,1427],"outerCode":"\n```js\n\nconst repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};\n\nconst doc = runner({ repoInfo }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${repo info}\n\n\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`**${r.stars}** stars — ${r.description}\\` )\n\\`\\`\\`\n\n\n```","outerHtml":"\n<p>```js</p>\n\n<p>const repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};</p>\n\n<p>const doc = runner({ repoInfo }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${repo info}</h2>\n\n<p>\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`<b>${r.stars}</b> stars — ${r.description}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>"},{"id":"/root/children/279/children/0","type":"text","loc":{"start":50906,"end":50929,"line":{"s":1402,"e":1402,"code":["### 1. Simple lookup (sync)"]},"column":{"s":4,"e":27}},"dim":["","heading.279","text.0"],"code":"1. Simple lookup (sync)"},{"id":"/root/children/280","type":"code","loc":{"start":50931,"end":51146,"line":{"s":1404,"e":1414,"code":["```js","","const repoInfo = {","ssss: { stars: 42, description: \"The ssss project\" },","mdt: { stars: 12, description: \"Markdown construction pseudo-code\" },","};","","const doc = runner({ repoInfo }, { evalFn: evalBody });","","","```"]},"column":{"s":0,"e":3}},"dim":["","code.280"],"code":"```js\n\nconst repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};\n\nconst doc = runner({ repoInfo }, { evalFn: evalBody });\n\n\n```","symbName":"code","symbRange":[51148,58640],"symbRangeL":[null,1415],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```\n\n### How it works\n\nGiven this setup:\n\n```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```\n\nAn extruction body like:\n\n\n```\n## ${find stuff}\n\n\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```\n\n...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.\n\n### Naming rules\n\n- Keys must be **valid JS identifiers** (no hyphens, no leading digits)\n- Use **camelCase** — this is idiomatic for JS function names\n- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)\n- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`\n\n### Return protocol\n\nAdapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:\n\n- `return insert(value)` — the extruction produces output\n- `return undefined` or no return — extruction stays transparent\n- `throw error` — propagates to the consumer (or caught by `onExtructionError`)\n\nThis means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.\n\n### Adapter conventions\n\n1. **Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.\n\n2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.\n\n3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:\n\n\n```\n   ## ${search mdd}\n\n   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```\n\nThis is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.\n\n## Example adapters\n\n### 1. Simple lookup (sync)\n\n```js\n\nconst repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};\n\nconst doc = runner({ repoInfo }, { evalFn: evalBody });\n\n\n```","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>\n\n<h3>How it works</h3>\n\n<p>Given this setup:</p>\n\n<p>```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```</p>\n\n<p>An extruction body like:</p>\n\n\n<p>```</p><h2>${find stuff}</h2>\n\n<p>\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<p>...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.</p>\n\n<h3>Naming rules</h3>\n\n<ul><li>Keys must be <b>valid JS identifiers</b> (no hyphens, no leading digits)</li><li>Use <b>camelCase</b> — this is idiomatic for JS function names</li><li>Avoid the `<i>mdt</i>` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)</li><li>Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`</li></ul>\n\n<h3>Return protocol</h3>\n\n<p>Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:</p>\n\n<ul><li>`return insert(value)` — the extruction produces output</li><li>`return undefined` or no return — extruction stays transparent</li><li>`throw error` — propagates to the consumer (or caught by `onExtructionError`)</li></ul>\n\n<p>This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.</p>\n\n<h3>Adapter conventions</h3>\n\n<ol><li><b>Async by convention</b> — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.</li></ol>\n\n<ol><li><b>Error handling</b> — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.</li></ol>\n\n<ol><li><b>`_mdt_label`</b> — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:</li></ol>\n\n\n<p>```</p><h2>${search mdd}</h2>\n\n<p>   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```</p>\n\n<p>This is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.</p>\n\n<h2>Example adapters</h2>\n\n<h3>1. Simple lookup (sync)</h3>\n\n<p>```js</p>\n\n<p>const repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};</p>\n\n<p>const doc = runner({ repoInfo }, { evalFn: evalBody });</p>\n\n\n<p>```</p>"},{"id":"/root/children/281","type":"code","loc":{"start":51148,"end":51287,"line":{"s":1416,"e":1426,"code":["```","","## ${repo info}","","\\`\\`\\`javascript","const r = repoInfo[\"ssss\"]","return insert( \\`**${r.stars}** stars — ${r.description}\\` )","\\`\\`\\`","","","```"]},"column":{"s":0,"e":3}},"dim":["","code.281"],"code":"```\n\n## ${repo info}\n\n\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`**${r.stars}** stars — ${r.description}\\` )\n\\`\\`\\`\n\n\n```","symbName":"code","symbRange":[51289,51381],"symbRangeL":[null,1431],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```\n\n### How it works\n\nGiven this setup:\n\n```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```\n\nAn extruction body like:\n\n\n```\n## ${find stuff}\n\n\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```\n\n...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.\n\n### Naming rules\n\n- Keys must be **valid JS identifiers** (no hyphens, no leading digits)\n- Use **camelCase** — this is idiomatic for JS function names\n- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)\n- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`\n\n### Return protocol\n\nAdapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:\n\n- `return insert(value)` — the extruction produces output\n- `return undefined` or no return — extruction stays transparent\n- `throw error` — propagates to the consumer (or caught by `onExtructionError`)\n\nThis means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.\n\n### Adapter conventions\n\n1. **Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.\n\n2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.\n\n3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:\n\n\n```\n   ## ${search mdd}\n\n   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```\n\nThis is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.\n\n## Example adapters\n\n### 1. Simple lookup (sync)\n\n```js\n\nconst repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};\n\nconst doc = runner({ repoInfo }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${repo info}\n\n\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`**${r.stars}** stars — ${r.description}\\` )\n\\`\\`\\`\n\n\n```\n\n### 2. Search adapter\n\nAlready documented in [Search Adapter](#search-adapter). The pattern:","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>\n\n<h3>How it works</h3>\n\n<p>Given this setup:</p>\n\n<p>```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```</p>\n\n<p>An extruction body like:</p>\n\n\n<p>```</p><h2>${find stuff}</h2>\n\n<p>\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<p>...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.</p>\n\n<h3>Naming rules</h3>\n\n<ul><li>Keys must be <b>valid JS identifiers</b> (no hyphens, no leading digits)</li><li>Use <b>camelCase</b> — this is idiomatic for JS function names</li><li>Avoid the `<i>mdt</i>` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)</li><li>Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`</li></ul>\n\n<h3>Return protocol</h3>\n\n<p>Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:</p>\n\n<ul><li>`return insert(value)` — the extruction produces output</li><li>`return undefined` or no return — extruction stays transparent</li><li>`throw error` — propagates to the consumer (or caught by `onExtructionError`)</li></ul>\n\n<p>This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.</p>\n\n<h3>Adapter conventions</h3>\n\n<ol><li><b>Async by convention</b> — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.</li></ol>\n\n<ol><li><b>Error handling</b> — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.</li></ol>\n\n<ol><li><b>`_mdt_label`</b> — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:</li></ol>\n\n\n<p>```</p><h2>${search mdd}</h2>\n\n<p>   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```</p>\n\n<p>This is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.</p>\n\n<h2>Example adapters</h2>\n\n<h3>1. Simple lookup (sync)</h3>\n\n<p>```js</p>\n\n<p>const repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};</p>\n\n<p>const doc = runner({ repoInfo }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${repo info}</h2>\n\n<p>\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`<b>${r.stars}</b> stars — ${r.description}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>2. Search adapter</h3>\n\n<p>Already documented in <a href=\"#search-adapter\">Search Adapter</a>. The pattern:</p>"},{"id":"/root/children/282","type":"heading","loc":{"start":51289,"end":51310,"line":{"s":1428,"e":1428,"code":["### 2. Search adapter"]},"column":{"s":0,"e":21}},"dim":["","heading.282"],"code":"### 2. Search adapter","symbName":"heading","symbRange":[51312,51874],"symbRangeL":[1428,1458],"outerCode":"\nAlready documented in [Search Adapter](#search-adapter). The pattern:\n\n```js\n\nimport { search } from \"./mdt/search-adapter.js\";\n\nconst doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);\n\n\n```\n\n```\n\n## ${results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- [\\${i.name}](${i.uri})\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\nThe key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.","outerHtml":"\n<p>Already documented in <a href=\"#search-adapter\">Search Adapter</a>. The pattern:</p>\n\n<p>```js</p>\n\n<p>import { search } from \"./mdt/search-adapter.js\";</p>\n\n<p>const doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- <a href=\"${i.uri}\">\\${i.name}</a>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.</p>"},{"id":"/root/children/282/children/0","type":"text","loc":{"start":51293,"end":51310,"line":{"s":1428,"e":1428,"code":["### 2. Search adapter"]},"column":{"s":4,"e":21}},"dim":["","heading.282","text.0"],"code":"2. Search adapter"},{"id":"/root/children/283","type":"paragraph","loc":{"start":51312,"end":51381,"line":{"s":1430,"e":1430,"code":["Already documented in [Search Adapter](#search-adapter). The pattern:"]},"column":{"s":0,"e":69}},"dim":["","paragraph.283"],"code":"Already documented in [Search Adapter](#search-adapter). The pattern:"},{"id":"/root/children/283/children/0","type":"text","loc":{"start":51312,"end":51334,"line":{"s":1430,"e":1430,"code":["Already documented in [Search Adapter](#search-adapter). The pattern:"]},"column":{"s":0,"e":22}},"dim":["","paragraph.283","text.0"],"code":"Already documented in "},{"id":"/root/children/283/children/1","type":"link","loc":{"start":51334,"end":51367,"line":{"s":1430,"e":1430,"code":["Already documented in [Search Adapter](#search-adapter). The pattern:"]},"column":{"s":22,"e":55}},"dim":["","paragraph.283","link.1"],"code":"[Search Adapter](#search-adapter)","symbName":"link","symbRange":[51367,null],"symbRangeL":[1430,1429],"outerCode":"[Search Adapter](#search-adapter)","outerHtml":"<p><a href=\"#search-adapter\">Search Adapter</a></p>"},{"id":"/root/children/283/children/1/children/0","type":"text","loc":{"start":51335,"end":51349,"line":{"s":1430,"e":1430,"code":["Already documented in [Search Adapter](#search-adapter). The pattern:"]},"column":{"s":23,"e":37}},"dim":["","paragraph.283","link.1","text.0"],"code":"Search Adapter"},{"id":"/root/children/283/children/2","type":"text","loc":{"start":51367,"end":51381,"line":{"s":1430,"e":1430,"code":["Already documented in [Search Adapter](#search-adapter). The pattern:"]},"column":{"s":55,"e":69}},"dim":["","paragraph.283","text.2"],"code":". The pattern:"},{"id":"/root/children/284","type":"code","loc":{"start":51383,"end":51570,"line":{"s":1432,"e":1442,"code":["```js","","import { search } from \"./mdt/search-adapter.js\";","","const doc = runner(","{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },","{ evalFn: evalBody },",");","","","```"]},"column":{"s":0,"e":3}},"dim":["","code.284"],"code":"```js\n\nimport { search } from \"./mdt/search-adapter.js\";\n\nconst doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);\n\n\n```","symbName":"code","symbRange":[51572,58640],"symbRangeL":[null,1443],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```\n\n### How it works\n\nGiven this setup:\n\n```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```\n\nAn extruction body like:\n\n\n```\n## ${find stuff}\n\n\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```\n\n...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.\n\n### Naming rules\n\n- Keys must be **valid JS identifiers** (no hyphens, no leading digits)\n- Use **camelCase** — this is idiomatic for JS function names\n- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)\n- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`\n\n### Return protocol\n\nAdapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:\n\n- `return insert(value)` — the extruction produces output\n- `return undefined` or no return — extruction stays transparent\n- `throw error` — propagates to the consumer (or caught by `onExtructionError`)\n\nThis means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.\n\n### Adapter conventions\n\n1. **Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.\n\n2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.\n\n3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:\n\n\n```\n   ## ${search mdd}\n\n   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```\n\nThis is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.\n\n## Example adapters\n\n### 1. Simple lookup (sync)\n\n```js\n\nconst repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};\n\nconst doc = runner({ repoInfo }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${repo info}\n\n\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`**${r.stars}** stars — ${r.description}\\` )\n\\`\\`\\`\n\n\n```\n\n### 2. Search adapter\n\nAlready documented in [Search Adapter](#search-adapter). The pattern:\n\n```js\n\nimport { search } from \"./mdt/search-adapter.js\";\n\nconst doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);\n\n\n```","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>\n\n<h3>How it works</h3>\n\n<p>Given this setup:</p>\n\n<p>```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```</p>\n\n<p>An extruction body like:</p>\n\n\n<p>```</p><h2>${find stuff}</h2>\n\n<p>\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<p>...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.</p>\n\n<h3>Naming rules</h3>\n\n<ul><li>Keys must be <b>valid JS identifiers</b> (no hyphens, no leading digits)</li><li>Use <b>camelCase</b> — this is idiomatic for JS function names</li><li>Avoid the `<i>mdt</i>` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)</li><li>Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`</li></ul>\n\n<h3>Return protocol</h3>\n\n<p>Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:</p>\n\n<ul><li>`return insert(value)` — the extruction produces output</li><li>`return undefined` or no return — extruction stays transparent</li><li>`throw error` — propagates to the consumer (or caught by `onExtructionError`)</li></ul>\n\n<p>This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.</p>\n\n<h3>Adapter conventions</h3>\n\n<ol><li><b>Async by convention</b> — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.</li></ol>\n\n<ol><li><b>Error handling</b> — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.</li></ol>\n\n<ol><li><b>`_mdt_label`</b> — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:</li></ol>\n\n\n<p>```</p><h2>${search mdd}</h2>\n\n<p>   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```</p>\n\n<p>This is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.</p>\n\n<h2>Example adapters</h2>\n\n<h3>1. Simple lookup (sync)</h3>\n\n<p>```js</p>\n\n<p>const repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};</p>\n\n<p>const doc = runner({ repoInfo }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${repo info}</h2>\n\n<p>\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`<b>${r.stars}</b> stars — ${r.description}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>2. Search adapter</h3>\n\n<p>Already documented in <a href=\"#search-adapter\">Search Adapter</a>. The pattern:</p>\n\n<p>```js</p>\n\n<p>import { search } from \"./mdt/search-adapter.js\";</p>\n\n<p>const doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);</p>\n\n\n<p>```</p>"},{"id":"/root/children/285","type":"code","loc":{"start":51572,"end":51727,"line":{"s":1444,"e":1454,"code":["```","","## ${results}","","\\`\\`\\`javascript","const items = await search(\"mdd\")","return insert( items.map(i => \\`- [\\${i.name}](${i.uri})\\`).join(\"\\n\"))","\\`\\`\\`","","","```"]},"column":{"s":0,"e":3}},"dim":["","code.285"],"code":"```\n\n## ${results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- [\\${i.name}](${i.uri})\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```","symbName":"code","symbRange":[51729,51893],"symbRangeL":[null,1460],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```\n\n### How it works\n\nGiven this setup:\n\n```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```\n\nAn extruction body like:\n\n\n```\n## ${find stuff}\n\n\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```\n\n...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.\n\n### Naming rules\n\n- Keys must be **valid JS identifiers** (no hyphens, no leading digits)\n- Use **camelCase** — this is idiomatic for JS function names\n- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)\n- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`\n\n### Return protocol\n\nAdapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:\n\n- `return insert(value)` — the extruction produces output\n- `return undefined` or no return — extruction stays transparent\n- `throw error` — propagates to the consumer (or caught by `onExtructionError`)\n\nThis means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.\n\n### Adapter conventions\n\n1. **Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.\n\n2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.\n\n3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:\n\n\n```\n   ## ${search mdd}\n\n   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```\n\nThis is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.\n\n## Example adapters\n\n### 1. Simple lookup (sync)\n\n```js\n\nconst repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};\n\nconst doc = runner({ repoInfo }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${repo info}\n\n\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`**${r.stars}** stars — ${r.description}\\` )\n\\`\\`\\`\n\n\n```\n\n### 2. Search adapter\n\nAlready documented in [Search Adapter](#search-adapter). The pattern:\n\n```js\n\nimport { search } from \"./mdt/search-adapter.js\";\n\nconst doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);\n\n\n```\n\n```\n\n## ${results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- [\\${i.name}](${i.uri})\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\nThe key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.\n\n### 3. HTTP fetch","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>\n\n<h3>How it works</h3>\n\n<p>Given this setup:</p>\n\n<p>```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```</p>\n\n<p>An extruction body like:</p>\n\n\n<p>```</p><h2>${find stuff}</h2>\n\n<p>\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<p>...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.</p>\n\n<h3>Naming rules</h3>\n\n<ul><li>Keys must be <b>valid JS identifiers</b> (no hyphens, no leading digits)</li><li>Use <b>camelCase</b> — this is idiomatic for JS function names</li><li>Avoid the `<i>mdt</i>` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)</li><li>Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`</li></ul>\n\n<h3>Return protocol</h3>\n\n<p>Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:</p>\n\n<ul><li>`return insert(value)` — the extruction produces output</li><li>`return undefined` or no return — extruction stays transparent</li><li>`throw error` — propagates to the consumer (or caught by `onExtructionError`)</li></ul>\n\n<p>This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.</p>\n\n<h3>Adapter conventions</h3>\n\n<ol><li><b>Async by convention</b> — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.</li></ol>\n\n<ol><li><b>Error handling</b> — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.</li></ol>\n\n<ol><li><b>`_mdt_label`</b> — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:</li></ol>\n\n\n<p>```</p><h2>${search mdd}</h2>\n\n<p>   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```</p>\n\n<p>This is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.</p>\n\n<h2>Example adapters</h2>\n\n<h3>1. Simple lookup (sync)</h3>\n\n<p>```js</p>\n\n<p>const repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};</p>\n\n<p>const doc = runner({ repoInfo }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${repo info}</h2>\n\n<p>\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`<b>${r.stars}</b> stars — ${r.description}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>2. Search adapter</h3>\n\n<p>Already documented in <a href=\"#search-adapter\">Search Adapter</a>. The pattern:</p>\n\n<p>```js</p>\n\n<p>import { search } from \"./mdt/search-adapter.js\";</p>\n\n<p>const doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- <a href=\"${i.uri}\">\\${i.name}</a>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.</p>\n\n<h3>3. HTTP fetch</h3>"},{"id":"/root/children/286","type":"paragraph","loc":{"start":51729,"end":51874,"line":{"s":1456,"e":1457,"code":["The key insight: the adapter wraps the app's async search with completion","detection, but the extruction body just sees a function it can `await`."]},"column":{"s":0,"e":71}},"dim":["","paragraph.286"],"code":"The key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`."},{"id":"/root/children/286/children/0","type":"text","loc":{"start":51729,"end":51866,"line":{"s":1456,"e":1457,"code":["The key insight: the adapter wraps the app's async search with completion","detection, but the extruction body just sees a function it can `await`."]},"column":{"s":0,"e":63}},"dim":["","paragraph.286","text.0"],"code":"The key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can "},{"id":"/root/children/286/children/1","type":"inlineCode","loc":{"start":51866,"end":51873,"line":{"s":1457,"e":1457,"code":["detection, but the extruction body just sees a function it can `await`."]},"column":{"s":63,"e":70}},"dim":["","paragraph.286","inlineCode.1"],"code":"`await`"},{"id":"/root/children/286/children/2","type":"text","loc":{"start":51873,"end":51874,"line":{"s":1457,"e":1457,"code":["detection, but the extruction body just sees a function it can `await`."]},"column":{"s":70,"e":71}},"dim":["","paragraph.286","text.2"],"code":"."},{"id":"/root/children/287","type":"heading","loc":{"start":51876,"end":51893,"line":{"s":1459,"e":1459,"code":["### 3. HTTP fetch"]},"column":{"s":0,"e":17}},"dim":["","heading.287"],"code":"### 3. HTTP fetch","symbName":"heading","symbRange":[51895,52499],"symbRangeL":[1459,1491],"outerCode":"\n```js\n\nconst fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};\n\nconst doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);\n\n\n```\n\n```\n\n## ${github stats}\n\n\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`\n\n\n```\n\nThe adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.","outerHtml":"\n<p>```js</p>\n\n<p>const fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};</p>\n\n<p>const doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${github stats}</h2>\n\n<p>\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.</p>"},{"id":"/root/children/287/children/0","type":"text","loc":{"start":51880,"end":51893,"line":{"s":1459,"e":1459,"code":["### 3. HTTP fetch"]},"column":{"s":4,"e":17}},"dim":["","heading.287","text.0"],"code":"3. HTTP fetch"},{"id":"/root/children/288","type":"code","loc":{"start":51895,"end":52148,"line":{"s":1461,"e":1475,"code":["```js","","const fetchJson = async (url) => {","const res = await fetch(url);","if (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);","return res.json();","};","","const doc = runner(","{ fetchJson },","{ evalFn: evalBody, onExtructionError: handleError },",");","","","```"]},"column":{"s":0,"e":3}},"dim":["","code.288"],"code":"```js\n\nconst fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};\n\nconst doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);\n\n\n```","symbName":"code","symbRange":[52150,58640],"symbRangeL":[null,1476],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```\n\n### How it works\n\nGiven this setup:\n\n```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```\n\nAn extruction body like:\n\n\n```\n## ${find stuff}\n\n\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```\n\n...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.\n\n### Naming rules\n\n- Keys must be **valid JS identifiers** (no hyphens, no leading digits)\n- Use **camelCase** — this is idiomatic for JS function names\n- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)\n- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`\n\n### Return protocol\n\nAdapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:\n\n- `return insert(value)` — the extruction produces output\n- `return undefined` or no return — extruction stays transparent\n- `throw error` — propagates to the consumer (or caught by `onExtructionError`)\n\nThis means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.\n\n### Adapter conventions\n\n1. **Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.\n\n2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.\n\n3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:\n\n\n```\n   ## ${search mdd}\n\n   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```\n\nThis is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.\n\n## Example adapters\n\n### 1. Simple lookup (sync)\n\n```js\n\nconst repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};\n\nconst doc = runner({ repoInfo }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${repo info}\n\n\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`**${r.stars}** stars — ${r.description}\\` )\n\\`\\`\\`\n\n\n```\n\n### 2. Search adapter\n\nAlready documented in [Search Adapter](#search-adapter). The pattern:\n\n```js\n\nimport { search } from \"./mdt/search-adapter.js\";\n\nconst doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);\n\n\n```\n\n```\n\n## ${results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- [\\${i.name}](${i.uri})\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\nThe key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.\n\n### 3. HTTP fetch\n\n```js\n\nconst fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};\n\nconst doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);\n\n\n```","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>\n\n<h3>How it works</h3>\n\n<p>Given this setup:</p>\n\n<p>```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```</p>\n\n<p>An extruction body like:</p>\n\n\n<p>```</p><h2>${find stuff}</h2>\n\n<p>\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<p>...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.</p>\n\n<h3>Naming rules</h3>\n\n<ul><li>Keys must be <b>valid JS identifiers</b> (no hyphens, no leading digits)</li><li>Use <b>camelCase</b> — this is idiomatic for JS function names</li><li>Avoid the `<i>mdt</i>` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)</li><li>Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`</li></ul>\n\n<h3>Return protocol</h3>\n\n<p>Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:</p>\n\n<ul><li>`return insert(value)` — the extruction produces output</li><li>`return undefined` or no return — extruction stays transparent</li><li>`throw error` — propagates to the consumer (or caught by `onExtructionError`)</li></ul>\n\n<p>This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.</p>\n\n<h3>Adapter conventions</h3>\n\n<ol><li><b>Async by convention</b> — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.</li></ol>\n\n<ol><li><b>Error handling</b> — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.</li></ol>\n\n<ol><li><b>`_mdt_label`</b> — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:</li></ol>\n\n\n<p>```</p><h2>${search mdd}</h2>\n\n<p>   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```</p>\n\n<p>This is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.</p>\n\n<h2>Example adapters</h2>\n\n<h3>1. Simple lookup (sync)</h3>\n\n<p>```js</p>\n\n<p>const repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};</p>\n\n<p>const doc = runner({ repoInfo }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${repo info}</h2>\n\n<p>\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`<b>${r.stars}</b> stars — ${r.description}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>2. Search adapter</h3>\n\n<p>Already documented in <a href=\"#search-adapter\">Search Adapter</a>. The pattern:</p>\n\n<p>```js</p>\n\n<p>import { search } from \"./mdt/search-adapter.js\";</p>\n\n<p>const doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- <a href=\"${i.uri}\">\\${i.name}</a>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.</p>\n\n<h3>3. HTTP fetch</h3>\n\n<p>```js</p>\n\n<p>const fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};</p>\n\n<p>const doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);</p>\n\n\n<p>```</p>"},{"id":"/root/children/289","type":"code","loc":{"start":52150,"end":52356,"line":{"s":1477,"e":1487,"code":["```","","## ${github stats}","","\\`\\`\\`javascript","const data = await fetchJson(\"https://api.github.com/repos/user/repo\")","return insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )","\\`\\`\\`","","","```"]},"column":{"s":0,"e":3}},"dim":["","code.289"],"code":"```\n\n## ${github stats}\n\n\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`\n\n\n```","symbName":"code","symbRange":[52358,52522],"symbRangeL":[null,1493],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```\n\n### How it works\n\nGiven this setup:\n\n```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```\n\nAn extruction body like:\n\n\n```\n## ${find stuff}\n\n\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```\n\n...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.\n\n### Naming rules\n\n- Keys must be **valid JS identifiers** (no hyphens, no leading digits)\n- Use **camelCase** — this is idiomatic for JS function names\n- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)\n- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`\n\n### Return protocol\n\nAdapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:\n\n- `return insert(value)` — the extruction produces output\n- `return undefined` or no return — extruction stays transparent\n- `throw error` — propagates to the consumer (or caught by `onExtructionError`)\n\nThis means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.\n\n### Adapter conventions\n\n1. **Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.\n\n2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.\n\n3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:\n\n\n```\n   ## ${search mdd}\n\n   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```\n\nThis is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.\n\n## Example adapters\n\n### 1. Simple lookup (sync)\n\n```js\n\nconst repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};\n\nconst doc = runner({ repoInfo }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${repo info}\n\n\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`**${r.stars}** stars — ${r.description}\\` )\n\\`\\`\\`\n\n\n```\n\n### 2. Search adapter\n\nAlready documented in [Search Adapter](#search-adapter). The pattern:\n\n```js\n\nimport { search } from \"./mdt/search-adapter.js\";\n\nconst doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);\n\n\n```\n\n```\n\n## ${results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- [\\${i.name}](${i.uri})\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\nThe key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.\n\n### 3. HTTP fetch\n\n```js\n\nconst fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};\n\nconst doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);\n\n\n```\n\n```\n\n## ${github stats}\n\n\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`\n\n\n```\n\nThe adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.\n\n### 4. Database query","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>\n\n<h3>How it works</h3>\n\n<p>Given this setup:</p>\n\n<p>```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```</p>\n\n<p>An extruction body like:</p>\n\n\n<p>```</p><h2>${find stuff}</h2>\n\n<p>\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<p>...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.</p>\n\n<h3>Naming rules</h3>\n\n<ul><li>Keys must be <b>valid JS identifiers</b> (no hyphens, no leading digits)</li><li>Use <b>camelCase</b> — this is idiomatic for JS function names</li><li>Avoid the `<i>mdt</i>` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)</li><li>Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`</li></ul>\n\n<h3>Return protocol</h3>\n\n<p>Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:</p>\n\n<ul><li>`return insert(value)` — the extruction produces output</li><li>`return undefined` or no return — extruction stays transparent</li><li>`throw error` — propagates to the consumer (or caught by `onExtructionError`)</li></ul>\n\n<p>This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.</p>\n\n<h3>Adapter conventions</h3>\n\n<ol><li><b>Async by convention</b> — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.</li></ol>\n\n<ol><li><b>Error handling</b> — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.</li></ol>\n\n<ol><li><b>`_mdt_label`</b> — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:</li></ol>\n\n\n<p>```</p><h2>${search mdd}</h2>\n\n<p>   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```</p>\n\n<p>This is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.</p>\n\n<h2>Example adapters</h2>\n\n<h3>1. Simple lookup (sync)</h3>\n\n<p>```js</p>\n\n<p>const repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};</p>\n\n<p>const doc = runner({ repoInfo }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${repo info}</h2>\n\n<p>\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`<b>${r.stars}</b> stars — ${r.description}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>2. Search adapter</h3>\n\n<p>Already documented in <a href=\"#search-adapter\">Search Adapter</a>. The pattern:</p>\n\n<p>```js</p>\n\n<p>import { search } from \"./mdt/search-adapter.js\";</p>\n\n<p>const doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- <a href=\"${i.uri}\">\\${i.name}</a>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.</p>\n\n<h3>3. HTTP fetch</h3>\n\n<p>```js</p>\n\n<p>const fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};</p>\n\n<p>const doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${github stats}</h2>\n\n<p>\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.</p>\n\n<h3>4. Database query</h3>"},{"id":"/root/children/290","type":"paragraph","loc":{"start":52358,"end":52499,"line":{"s":1489,"e":1490,"code":["The adapter is a thin wrapper around `fetch()` with error handling.","The extruction body destructures the response and formats it as markdown."]},"column":{"s":0,"e":73}},"dim":["","paragraph.290"],"code":"The adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown."},{"id":"/root/children/290/children/0","type":"text","loc":{"start":52358,"end":52395,"line":{"s":1489,"e":1489,"code":["The adapter is a thin wrapper around `fetch()` with error handling."]},"column":{"s":0,"e":37}},"dim":["","paragraph.290","text.0"],"code":"The adapter is a thin wrapper around "},{"id":"/root/children/290/children/1","type":"inlineCode","loc":{"start":52395,"end":52404,"line":{"s":1489,"e":1489,"code":["The adapter is a thin wrapper around `fetch()` with error handling."]},"column":{"s":37,"e":46}},"dim":["","paragraph.290","inlineCode.1"],"code":"`fetch()`"},{"id":"/root/children/290/children/2","type":"text","loc":{"start":52404,"end":52499,"line":{"s":1489,"e":1490,"code":["The adapter is a thin wrapper around `fetch()` with error handling.","The extruction body destructures the response and formats it as markdown."]},"column":{"s":46,"e":73}},"dim":["","paragraph.290","text.2"],"code":" with error handling.\nThe extruction body destructures the response and formats it as markdown."},{"id":"/root/children/291","type":"heading","loc":{"start":52501,"end":52522,"line":{"s":1492,"e":1492,"code":["### 4. Database query"]},"column":{"s":0,"e":21}},"dim":["","heading.291"],"code":"### 4. Database query","symbName":"heading","symbRange":[52524,52887],"symbRangeL":[1492,1517],"outerCode":"\n```js\n\nconst queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};\n\nconst doc = runner({ queryDb }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${active users}\n\n\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```","outerHtml":"\n<p>```js</p>\n\n<p>const queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};</p>\n\n<p>const doc = runner({ queryDb }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${active users}</h2>\n\n<p>\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>"},{"id":"/root/children/291/children/0","type":"text","loc":{"start":52505,"end":52522,"line":{"s":1492,"e":1492,"code":["### 4. Database query"]},"column":{"s":4,"e":21}},"dim":["","heading.291","text.0"],"code":"4. Database query"},{"id":"/root/children/292","type":"code","loc":{"start":52524,"end":52681,"line":{"s":1494,"e":1504,"code":["```js","","const queryDb = async (sql) => {","const db = await getDatabase();","return db.exec(sql);","};","","const doc = runner({ queryDb }, { evalFn: evalBody });","","","```"]},"column":{"s":0,"e":3}},"dim":["","code.292"],"code":"```js\n\nconst queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};\n\nconst doc = runner({ queryDb }, { evalFn: evalBody });\n\n\n```","symbName":"code","symbRange":[52683,58640],"symbRangeL":[null,1505],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```\n\n### How it works\n\nGiven this setup:\n\n```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```\n\nAn extruction body like:\n\n\n```\n## ${find stuff}\n\n\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```\n\n...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.\n\n### Naming rules\n\n- Keys must be **valid JS identifiers** (no hyphens, no leading digits)\n- Use **camelCase** — this is idiomatic for JS function names\n- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)\n- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`\n\n### Return protocol\n\nAdapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:\n\n- `return insert(value)` — the extruction produces output\n- `return undefined` or no return — extruction stays transparent\n- `throw error` — propagates to the consumer (or caught by `onExtructionError`)\n\nThis means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.\n\n### Adapter conventions\n\n1. **Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.\n\n2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.\n\n3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:\n\n\n```\n   ## ${search mdd}\n\n   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```\n\nThis is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.\n\n## Example adapters\n\n### 1. Simple lookup (sync)\n\n```js\n\nconst repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};\n\nconst doc = runner({ repoInfo }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${repo info}\n\n\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`**${r.stars}** stars — ${r.description}\\` )\n\\`\\`\\`\n\n\n```\n\n### 2. Search adapter\n\nAlready documented in [Search Adapter](#search-adapter). The pattern:\n\n```js\n\nimport { search } from \"./mdt/search-adapter.js\";\n\nconst doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);\n\n\n```\n\n```\n\n## ${results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- [\\${i.name}](${i.uri})\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\nThe key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.\n\n### 3. HTTP fetch\n\n```js\n\nconst fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};\n\nconst doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);\n\n\n```\n\n```\n\n## ${github stats}\n\n\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`\n\n\n```\n\nThe adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.\n\n### 4. Database query\n\n```js\n\nconst queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};\n\nconst doc = runner({ queryDb }, { evalFn: evalBody });\n\n\n```","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>\n\n<h3>How it works</h3>\n\n<p>Given this setup:</p>\n\n<p>```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```</p>\n\n<p>An extruction body like:</p>\n\n\n<p>```</p><h2>${find stuff}</h2>\n\n<p>\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<p>...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.</p>\n\n<h3>Naming rules</h3>\n\n<ul><li>Keys must be <b>valid JS identifiers</b> (no hyphens, no leading digits)</li><li>Use <b>camelCase</b> — this is idiomatic for JS function names</li><li>Avoid the `<i>mdt</i>` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)</li><li>Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`</li></ul>\n\n<h3>Return protocol</h3>\n\n<p>Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:</p>\n\n<ul><li>`return insert(value)` — the extruction produces output</li><li>`return undefined` or no return — extruction stays transparent</li><li>`throw error` — propagates to the consumer (or caught by `onExtructionError`)</li></ul>\n\n<p>This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.</p>\n\n<h3>Adapter conventions</h3>\n\n<ol><li><b>Async by convention</b> — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.</li></ol>\n\n<ol><li><b>Error handling</b> — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.</li></ol>\n\n<ol><li><b>`_mdt_label`</b> — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:</li></ol>\n\n\n<p>```</p><h2>${search mdd}</h2>\n\n<p>   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```</p>\n\n<p>This is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.</p>\n\n<h2>Example adapters</h2>\n\n<h3>1. Simple lookup (sync)</h3>\n\n<p>```js</p>\n\n<p>const repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};</p>\n\n<p>const doc = runner({ repoInfo }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${repo info}</h2>\n\n<p>\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`<b>${r.stars}</b> stars — ${r.description}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>2. Search adapter</h3>\n\n<p>Already documented in <a href=\"#search-adapter\">Search Adapter</a>. The pattern:</p>\n\n<p>```js</p>\n\n<p>import { search } from \"./mdt/search-adapter.js\";</p>\n\n<p>const doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- <a href=\"${i.uri}\">\\${i.name}</a>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.</p>\n\n<h3>3. HTTP fetch</h3>\n\n<p>```js</p>\n\n<p>const fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};</p>\n\n<p>const doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${github stats}</h2>\n\n<p>\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.</p>\n\n<h3>4. Database query</h3>\n\n<p>```js</p>\n\n<p>const queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};</p>\n\n<p>const doc = runner({ queryDb }, { evalFn: evalBody });</p>\n\n\n<p>```</p>"},{"id":"/root/children/293","type":"code","loc":{"start":52683,"end":52887,"line":{"s":1506,"e":1516,"code":["```","","## ${active users}","","\\`\\`\\`javascript","const rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")","return insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))","\\`\\`\\`","","","```"]},"column":{"s":0,"e":3}},"dim":["","code.293"],"code":"```\n\n## ${active users}\n\n\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```","symbName":"code","symbRange":[52889,53005],"symbRangeL":[null,1522],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```\n\n### How it works\n\nGiven this setup:\n\n```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```\n\nAn extruction body like:\n\n\n```\n## ${find stuff}\n\n\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```\n\n...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.\n\n### Naming rules\n\n- Keys must be **valid JS identifiers** (no hyphens, no leading digits)\n- Use **camelCase** — this is idiomatic for JS function names\n- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)\n- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`\n\n### Return protocol\n\nAdapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:\n\n- `return insert(value)` — the extruction produces output\n- `return undefined` or no return — extruction stays transparent\n- `throw error` — propagates to the consumer (or caught by `onExtructionError`)\n\nThis means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.\n\n### Adapter conventions\n\n1. **Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.\n\n2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.\n\n3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:\n\n\n```\n   ## ${search mdd}\n\n   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```\n\nThis is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.\n\n## Example adapters\n\n### 1. Simple lookup (sync)\n\n```js\n\nconst repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};\n\nconst doc = runner({ repoInfo }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${repo info}\n\n\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`**${r.stars}** stars — ${r.description}\\` )\n\\`\\`\\`\n\n\n```\n\n### 2. Search adapter\n\nAlready documented in [Search Adapter](#search-adapter). The pattern:\n\n```js\n\nimport { search } from \"./mdt/search-adapter.js\";\n\nconst doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);\n\n\n```\n\n```\n\n## ${results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- [\\${i.name}](${i.uri})\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\nThe key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.\n\n### 3. HTTP fetch\n\n```js\n\nconst fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};\n\nconst doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);\n\n\n```\n\n```\n\n## ${github stats}\n\n\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`\n\n\n```\n\nThe adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.\n\n### 4. Database query\n\n```js\n\nconst queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};\n\nconst doc = runner({ queryDb }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${active users}\n\n\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\n### 5. State access\n\nWhen the runner context includes the app's state object, extructions can read\nfrom it directly:","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>\n\n<h3>How it works</h3>\n\n<p>Given this setup:</p>\n\n<p>```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```</p>\n\n<p>An extruction body like:</p>\n\n\n<p>```</p><h2>${find stuff}</h2>\n\n<p>\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<p>...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.</p>\n\n<h3>Naming rules</h3>\n\n<ul><li>Keys must be <b>valid JS identifiers</b> (no hyphens, no leading digits)</li><li>Use <b>camelCase</b> — this is idiomatic for JS function names</li><li>Avoid the `<i>mdt</i>` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)</li><li>Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`</li></ul>\n\n<h3>Return protocol</h3>\n\n<p>Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:</p>\n\n<ul><li>`return insert(value)` — the extruction produces output</li><li>`return undefined` or no return — extruction stays transparent</li><li>`throw error` — propagates to the consumer (or caught by `onExtructionError`)</li></ul>\n\n<p>This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.</p>\n\n<h3>Adapter conventions</h3>\n\n<ol><li><b>Async by convention</b> — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.</li></ol>\n\n<ol><li><b>Error handling</b> — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.</li></ol>\n\n<ol><li><b>`_mdt_label`</b> — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:</li></ol>\n\n\n<p>```</p><h2>${search mdd}</h2>\n\n<p>   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```</p>\n\n<p>This is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.</p>\n\n<h2>Example adapters</h2>\n\n<h3>1. Simple lookup (sync)</h3>\n\n<p>```js</p>\n\n<p>const repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};</p>\n\n<p>const doc = runner({ repoInfo }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${repo info}</h2>\n\n<p>\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`<b>${r.stars}</b> stars — ${r.description}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>2. Search adapter</h3>\n\n<p>Already documented in <a href=\"#search-adapter\">Search Adapter</a>. The pattern:</p>\n\n<p>```js</p>\n\n<p>import { search } from \"./mdt/search-adapter.js\";</p>\n\n<p>const doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- <a href=\"${i.uri}\">\\${i.name}</a>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.</p>\n\n<h3>3. HTTP fetch</h3>\n\n<p>```js</p>\n\n<p>const fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};</p>\n\n<p>const doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${github stats}</h2>\n\n<p>\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.</p>\n\n<h3>4. Database query</h3>\n\n<p>```js</p>\n\n<p>const queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};</p>\n\n<p>const doc = runner({ queryDb }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${active users}</h2>\n\n<p>\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>5. State access</h3>\n\n<p>When the runner context includes the app's state object, extructions can read\nfrom it directly:</p>"},{"id":"/root/children/294","type":"heading","loc":{"start":52889,"end":52908,"line":{"s":1518,"e":1518,"code":["### 5. State access"]},"column":{"s":0,"e":19}},"dim":["","heading.294"],"code":"### 5. State access","symbName":"heading","symbRange":[52910,53317],"symbRangeL":[1518,1542],"outerCode":"\nWhen the runner context includes the app's state object, extructions can read\nfrom it directly:\n\n```js\n\nconst doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${welcome}\n\n\\`\\`\\`javascript\nreturn insert( \\`Hello **\\${currentUser}**, you have \\${STATE.notifications.length} notifications\\` )\n\\`\\`\\`\n\n\n```\n\nThis is how the app passes its reactive state into extruction bodies.","outerHtml":"\n<p>When the runner context includes the app's state object, extructions can read\nfrom it directly:</p>\n\n<p>```js</p>\n\n<p>const doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${welcome}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Hello <b>\\${currentUser}</b>, you have \\${STATE.notifications.length} notifications\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>This is how the app passes its reactive state into extruction bodies.</p>"},{"id":"/root/children/294/children/0","type":"text","loc":{"start":52893,"end":52908,"line":{"s":1518,"e":1518,"code":["### 5. State access"]},"column":{"s":4,"e":19}},"dim":["","heading.294","text.0"],"code":"5. State access"},{"id":"/root/children/295","type":"paragraph","loc":{"start":52910,"end":53005,"line":{"s":1520,"e":1521,"code":["When the runner context includes the app's state object, extructions can read","from it directly:"]},"column":{"s":0,"e":17}},"dim":["","paragraph.295"],"code":"When the runner context includes the app's state object, extructions can read\nfrom it directly:"},{"id":"/root/children/295/children/0","type":"text","loc":{"start":52910,"end":53005,"line":{"s":1520,"e":1521,"code":["When the runner context includes the app's state object, extructions can read","from it directly:"]},"column":{"s":0,"e":17}},"dim":["","paragraph.295","text.0"],"code":"When the runner context includes the app's state object, extructions can read\nfrom it directly:"},{"id":"/root/children/296","type":"code","loc":{"start":53007,"end":53093,"line":{"s":1523,"e":1528,"code":["```js","","const doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });","","","```"]},"column":{"s":0,"e":3}},"dim":["","code.296"],"code":"```js\n\nconst doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });\n\n\n```","symbName":"code","symbRange":[53095,58640],"symbRangeL":[null,1529],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```\n\n### How it works\n\nGiven this setup:\n\n```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```\n\nAn extruction body like:\n\n\n```\n## ${find stuff}\n\n\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```\n\n...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.\n\n### Naming rules\n\n- Keys must be **valid JS identifiers** (no hyphens, no leading digits)\n- Use **camelCase** — this is idiomatic for JS function names\n- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)\n- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`\n\n### Return protocol\n\nAdapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:\n\n- `return insert(value)` — the extruction produces output\n- `return undefined` or no return — extruction stays transparent\n- `throw error` — propagates to the consumer (or caught by `onExtructionError`)\n\nThis means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.\n\n### Adapter conventions\n\n1. **Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.\n\n2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.\n\n3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:\n\n\n```\n   ## ${search mdd}\n\n   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```\n\nThis is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.\n\n## Example adapters\n\n### 1. Simple lookup (sync)\n\n```js\n\nconst repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};\n\nconst doc = runner({ repoInfo }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${repo info}\n\n\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`**${r.stars}** stars — ${r.description}\\` )\n\\`\\`\\`\n\n\n```\n\n### 2. Search adapter\n\nAlready documented in [Search Adapter](#search-adapter). The pattern:\n\n```js\n\nimport { search } from \"./mdt/search-adapter.js\";\n\nconst doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);\n\n\n```\n\n```\n\n## ${results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- [\\${i.name}](${i.uri})\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\nThe key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.\n\n### 3. HTTP fetch\n\n```js\n\nconst fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};\n\nconst doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);\n\n\n```\n\n```\n\n## ${github stats}\n\n\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`\n\n\n```\n\nThe adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.\n\n### 4. Database query\n\n```js\n\nconst queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};\n\nconst doc = runner({ queryDb }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${active users}\n\n\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\n### 5. State access\n\nWhen the runner context includes the app's state object, extructions can read\nfrom it directly:\n\n```js\n\nconst doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });\n\n\n```","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>\n\n<h3>How it works</h3>\n\n<p>Given this setup:</p>\n\n<p>```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```</p>\n\n<p>An extruction body like:</p>\n\n\n<p>```</p><h2>${find stuff}</h2>\n\n<p>\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<p>...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.</p>\n\n<h3>Naming rules</h3>\n\n<ul><li>Keys must be <b>valid JS identifiers</b> (no hyphens, no leading digits)</li><li>Use <b>camelCase</b> — this is idiomatic for JS function names</li><li>Avoid the `<i>mdt</i>` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)</li><li>Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`</li></ul>\n\n<h3>Return protocol</h3>\n\n<p>Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:</p>\n\n<ul><li>`return insert(value)` — the extruction produces output</li><li>`return undefined` or no return — extruction stays transparent</li><li>`throw error` — propagates to the consumer (or caught by `onExtructionError`)</li></ul>\n\n<p>This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.</p>\n\n<h3>Adapter conventions</h3>\n\n<ol><li><b>Async by convention</b> — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.</li></ol>\n\n<ol><li><b>Error handling</b> — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.</li></ol>\n\n<ol><li><b>`_mdt_label`</b> — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:</li></ol>\n\n\n<p>```</p><h2>${search mdd}</h2>\n\n<p>   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```</p>\n\n<p>This is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.</p>\n\n<h2>Example adapters</h2>\n\n<h3>1. Simple lookup (sync)</h3>\n\n<p>```js</p>\n\n<p>const repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};</p>\n\n<p>const doc = runner({ repoInfo }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${repo info}</h2>\n\n<p>\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`<b>${r.stars}</b> stars — ${r.description}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>2. Search adapter</h3>\n\n<p>Already documented in <a href=\"#search-adapter\">Search Adapter</a>. The pattern:</p>\n\n<p>```js</p>\n\n<p>import { search } from \"./mdt/search-adapter.js\";</p>\n\n<p>const doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- <a href=\"${i.uri}\">\\${i.name}</a>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.</p>\n\n<h3>3. HTTP fetch</h3>\n\n<p>```js</p>\n\n<p>const fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};</p>\n\n<p>const doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${github stats}</h2>\n\n<p>\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.</p>\n\n<h3>4. Database query</h3>\n\n<p>```js</p>\n\n<p>const queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};</p>\n\n<p>const doc = runner({ queryDb }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${active users}</h2>\n\n<p>\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>5. State access</h3>\n\n<p>When the runner context includes the app's state object, extructions can read\nfrom it directly:</p>\n\n<p>```js</p>\n\n<p>const doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });</p>\n\n\n<p>```</p>"},{"id":"/root/children/297","type":"code","loc":{"start":53095,"end":53246,"line":{"s":1530,"e":1539,"code":["```","","## ${welcome}","","\\`\\`\\`javascript","return insert( \\`Hello **\\${currentUser}**, you have \\${STATE.notifications.length} notifications\\` )","\\`\\`\\`","","","```"]},"column":{"s":0,"e":3}},"dim":["","code.297"],"code":"```\n\n## ${welcome}\n\n\\`\\`\\`javascript\nreturn insert( \\`Hello **\\${currentUser}**, you have \\${STATE.notifications.length} notifications\\` )\n\\`\\`\\`\n\n\n```","symbName":"code","symbRange":[53248,53418],"symbRangeL":[null,1546],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```\n\n### How it works\n\nGiven this setup:\n\n```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```\n\nAn extruction body like:\n\n\n```\n## ${find stuff}\n\n\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```\n\n...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.\n\n### Naming rules\n\n- Keys must be **valid JS identifiers** (no hyphens, no leading digits)\n- Use **camelCase** — this is idiomatic for JS function names\n- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)\n- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`\n\n### Return protocol\n\nAdapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:\n\n- `return insert(value)` — the extruction produces output\n- `return undefined` or no return — extruction stays transparent\n- `throw error` — propagates to the consumer (or caught by `onExtructionError`)\n\nThis means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.\n\n### Adapter conventions\n\n1. **Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.\n\n2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.\n\n3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:\n\n\n```\n   ## ${search mdd}\n\n   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```\n\nThis is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.\n\n## Example adapters\n\n### 1. Simple lookup (sync)\n\n```js\n\nconst repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};\n\nconst doc = runner({ repoInfo }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${repo info}\n\n\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`**${r.stars}** stars — ${r.description}\\` )\n\\`\\`\\`\n\n\n```\n\n### 2. Search adapter\n\nAlready documented in [Search Adapter](#search-adapter). The pattern:\n\n```js\n\nimport { search } from \"./mdt/search-adapter.js\";\n\nconst doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);\n\n\n```\n\n```\n\n## ${results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- [\\${i.name}](${i.uri})\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\nThe key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.\n\n### 3. HTTP fetch\n\n```js\n\nconst fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};\n\nconst doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);\n\n\n```\n\n```\n\n## ${github stats}\n\n\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`\n\n\n```\n\nThe adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.\n\n### 4. Database query\n\n```js\n\nconst queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};\n\nconst doc = runner({ queryDb }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${active users}\n\n\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\n### 5. State access\n\nWhen the runner context includes the app's state object, extructions can read\nfrom it directly:\n\n```js\n\nconst doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${welcome}\n\n\\`\\`\\`javascript\nreturn insert( \\`Hello **\\${currentUser}**, you have \\${STATE.notifications.length} notifications\\` )\n\\`\\`\\`\n\n\n```\n\nThis is how the app passes its reactive state into extruction bodies.\n\n### 6. Composition — multiple adapters\n\nAdapters compose naturally since they're just JS functions:","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>\n\n<h3>How it works</h3>\n\n<p>Given this setup:</p>\n\n<p>```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```</p>\n\n<p>An extruction body like:</p>\n\n\n<p>```</p><h2>${find stuff}</h2>\n\n<p>\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<p>...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.</p>\n\n<h3>Naming rules</h3>\n\n<ul><li>Keys must be <b>valid JS identifiers</b> (no hyphens, no leading digits)</li><li>Use <b>camelCase</b> — this is idiomatic for JS function names</li><li>Avoid the `<i>mdt</i>` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)</li><li>Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`</li></ul>\n\n<h3>Return protocol</h3>\n\n<p>Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:</p>\n\n<ul><li>`return insert(value)` — the extruction produces output</li><li>`return undefined` or no return — extruction stays transparent</li><li>`throw error` — propagates to the consumer (or caught by `onExtructionError`)</li></ul>\n\n<p>This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.</p>\n\n<h3>Adapter conventions</h3>\n\n<ol><li><b>Async by convention</b> — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.</li></ol>\n\n<ol><li><b>Error handling</b> — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.</li></ol>\n\n<ol><li><b>`_mdt_label`</b> — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:</li></ol>\n\n\n<p>```</p><h2>${search mdd}</h2>\n\n<p>   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```</p>\n\n<p>This is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.</p>\n\n<h2>Example adapters</h2>\n\n<h3>1. Simple lookup (sync)</h3>\n\n<p>```js</p>\n\n<p>const repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};</p>\n\n<p>const doc = runner({ repoInfo }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${repo info}</h2>\n\n<p>\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`<b>${r.stars}</b> stars — ${r.description}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>2. Search adapter</h3>\n\n<p>Already documented in <a href=\"#search-adapter\">Search Adapter</a>. The pattern:</p>\n\n<p>```js</p>\n\n<p>import { search } from \"./mdt/search-adapter.js\";</p>\n\n<p>const doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- <a href=\"${i.uri}\">\\${i.name}</a>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.</p>\n\n<h3>3. HTTP fetch</h3>\n\n<p>```js</p>\n\n<p>const fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};</p>\n\n<p>const doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${github stats}</h2>\n\n<p>\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.</p>\n\n<h3>4. Database query</h3>\n\n<p>```js</p>\n\n<p>const queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};</p>\n\n<p>const doc = runner({ queryDb }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${active users}</h2>\n\n<p>\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>5. State access</h3>\n\n<p>When the runner context includes the app's state object, extructions can read\nfrom it directly:</p>\n\n<p>```js</p>\n\n<p>const doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${welcome}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Hello <b>\\${currentUser}</b>, you have \\${STATE.notifications.length} notifications\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>This is how the app passes its reactive state into extruction bodies.</p>\n\n<h3>6. Composition — multiple adapters</h3>\n\n<p>Adapters compose naturally since they're just JS functions:</p>"},{"id":"/root/children/298","type":"paragraph","loc":{"start":53248,"end":53317,"line":{"s":1541,"e":1541,"code":["This is how the app passes its reactive state into extruction bodies."]},"column":{"s":0,"e":69}},"dim":["","paragraph.298"],"code":"This is how the app passes its reactive state into extruction bodies."},{"id":"/root/children/298/children/0","type":"text","loc":{"start":53248,"end":53317,"line":{"s":1541,"e":1541,"code":["This is how the app passes its reactive state into extruction bodies."]},"column":{"s":0,"e":69}},"dim":["","paragraph.298","text.0"],"code":"This is how the app passes its reactive state into extruction bodies."},{"id":"/root/children/299","type":"heading","loc":{"start":53319,"end":53357,"line":{"s":1543,"e":1543,"code":["### 6. Composition — multiple adapters"]},"column":{"s":0,"e":38}},"dim":["","heading.299"],"code":"### 6. Composition — multiple adapters","symbName":"heading","symbRange":[53359,53962],"symbRangeL":[1543,1570],"outerCode":"\nAdapters compose naturally since they're just JS functions:\n\n```js\n\nconst doc = runner({ repoInfo, fetchJson, currentUser }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${dashboard}\n\n\\`\\`\\`javascript\nconst user = currentUser\nconst repos = await fetchJson(\\`https://api.github.com/users/\\${user}/repos\\`)\nconst summary = repos.map(r => \\`- \\${r.name}: \\${repoInfo[r.name]?.description || \"unknown\"}\\`).join(\"\\n\")\nreturn insert( \\`### \\${user}'s repos\\n\\n\\${summary}\\` )\n\\`\\`\\`\n\n\n```\n\nHere `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is\na plain string — all coexist as named parameters.","outerHtml":"\n<p>Adapters compose naturally since they're just JS functions:</p>\n\n<p>```js</p>\n\n<p>const doc = runner({ repoInfo, fetchJson, currentUser }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${dashboard}</h2>\n\n<p>\\`\\`\\`javascript\nconst user = currentUser\nconst repos = await fetchJson(\\`https://api.github.com/users/\\${user}/repos\\`)\nconst summary = repos.map(r => \\`- \\${r.name}: \\${repoInfo[r.name]?.description || \"unknown\"}\\`).join(\"\\n\")\nreturn insert( \\`### \\${user}'s repos\\n\\n\\${summary}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>Here `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is\na plain string — all coexist as named parameters.</p>"},{"id":"/root/children/299/children/0","type":"text","loc":{"start":53323,"end":53357,"line":{"s":1543,"e":1543,"code":["### 6. Composition — multiple adapters"]},"column":{"s":4,"e":38}},"dim":["","heading.299","text.0"],"code":"6. Composition — multiple adapters"},{"id":"/root/children/300","type":"paragraph","loc":{"start":53359,"end":53418,"line":{"s":1545,"e":1545,"code":["Adapters compose naturally since they're just JS functions:"]},"column":{"s":0,"e":59}},"dim":["","paragraph.300"],"code":"Adapters compose naturally since they're just JS functions:"},{"id":"/root/children/300/children/0","type":"text","loc":{"start":53359,"end":53418,"line":{"s":1545,"e":1545,"code":["Adapters compose naturally since they're just JS functions:"]},"column":{"s":0,"e":59}},"dim":["","paragraph.300","text.0"],"code":"Adapters compose naturally since they're just JS functions:"},{"id":"/root/children/301","type":"code","loc":{"start":53420,"end":53512,"line":{"s":1547,"e":1552,"code":["```js","","const doc = runner({ repoInfo, fetchJson, currentUser }, { evalFn: evalBody });","","","```"]},"column":{"s":0,"e":3}},"dim":["","code.301"],"code":"```js\n\nconst doc = runner({ repoInfo, fetchJson, currentUser }, { evalFn: evalBody });\n\n\n```","symbName":"code","symbRange":[53514,58640],"symbRangeL":[null,1553],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```\n\n### How it works\n\nGiven this setup:\n\n```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```\n\nAn extruction body like:\n\n\n```\n## ${find stuff}\n\n\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```\n\n...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.\n\n### Naming rules\n\n- Keys must be **valid JS identifiers** (no hyphens, no leading digits)\n- Use **camelCase** — this is idiomatic for JS function names\n- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)\n- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`\n\n### Return protocol\n\nAdapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:\n\n- `return insert(value)` — the extruction produces output\n- `return undefined` or no return — extruction stays transparent\n- `throw error` — propagates to the consumer (or caught by `onExtructionError`)\n\nThis means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.\n\n### Adapter conventions\n\n1. **Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.\n\n2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.\n\n3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:\n\n\n```\n   ## ${search mdd}\n\n   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```\n\nThis is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.\n\n## Example adapters\n\n### 1. Simple lookup (sync)\n\n```js\n\nconst repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};\n\nconst doc = runner({ repoInfo }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${repo info}\n\n\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`**${r.stars}** stars — ${r.description}\\` )\n\\`\\`\\`\n\n\n```\n\n### 2. Search adapter\n\nAlready documented in [Search Adapter](#search-adapter). The pattern:\n\n```js\n\nimport { search } from \"./mdt/search-adapter.js\";\n\nconst doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);\n\n\n```\n\n```\n\n## ${results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- [\\${i.name}](${i.uri})\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\nThe key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.\n\n### 3. HTTP fetch\n\n```js\n\nconst fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};\n\nconst doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);\n\n\n```\n\n```\n\n## ${github stats}\n\n\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`\n\n\n```\n\nThe adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.\n\n### 4. Database query\n\n```js\n\nconst queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};\n\nconst doc = runner({ queryDb }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${active users}\n\n\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\n### 5. State access\n\nWhen the runner context includes the app's state object, extructions can read\nfrom it directly:\n\n```js\n\nconst doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${welcome}\n\n\\`\\`\\`javascript\nreturn insert( \\`Hello **\\${currentUser}**, you have \\${STATE.notifications.length} notifications\\` )\n\\`\\`\\`\n\n\n```\n\nThis is how the app passes its reactive state into extruction bodies.\n\n### 6. Composition — multiple adapters\n\nAdapters compose naturally since they're just JS functions:\n\n```js\n\nconst doc = runner({ repoInfo, fetchJson, currentUser }, { evalFn: evalBody });\n\n\n```","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>\n\n<h3>How it works</h3>\n\n<p>Given this setup:</p>\n\n<p>```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```</p>\n\n<p>An extruction body like:</p>\n\n\n<p>```</p><h2>${find stuff}</h2>\n\n<p>\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<p>...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.</p>\n\n<h3>Naming rules</h3>\n\n<ul><li>Keys must be <b>valid JS identifiers</b> (no hyphens, no leading digits)</li><li>Use <b>camelCase</b> — this is idiomatic for JS function names</li><li>Avoid the `<i>mdt</i>` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)</li><li>Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`</li></ul>\n\n<h3>Return protocol</h3>\n\n<p>Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:</p>\n\n<ul><li>`return insert(value)` — the extruction produces output</li><li>`return undefined` or no return — extruction stays transparent</li><li>`throw error` — propagates to the consumer (or caught by `onExtructionError`)</li></ul>\n\n<p>This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.</p>\n\n<h3>Adapter conventions</h3>\n\n<ol><li><b>Async by convention</b> — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.</li></ol>\n\n<ol><li><b>Error handling</b> — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.</li></ol>\n\n<ol><li><b>`_mdt_label`</b> — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:</li></ol>\n\n\n<p>```</p><h2>${search mdd}</h2>\n\n<p>   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```</p>\n\n<p>This is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.</p>\n\n<h2>Example adapters</h2>\n\n<h3>1. Simple lookup (sync)</h3>\n\n<p>```js</p>\n\n<p>const repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};</p>\n\n<p>const doc = runner({ repoInfo }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${repo info}</h2>\n\n<p>\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`<b>${r.stars}</b> stars — ${r.description}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>2. Search adapter</h3>\n\n<p>Already documented in <a href=\"#search-adapter\">Search Adapter</a>. The pattern:</p>\n\n<p>```js</p>\n\n<p>import { search } from \"./mdt/search-adapter.js\";</p>\n\n<p>const doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- <a href=\"${i.uri}\">\\${i.name}</a>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.</p>\n\n<h3>3. HTTP fetch</h3>\n\n<p>```js</p>\n\n<p>const fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};</p>\n\n<p>const doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${github stats}</h2>\n\n<p>\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.</p>\n\n<h3>4. Database query</h3>\n\n<p>```js</p>\n\n<p>const queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};</p>\n\n<p>const doc = runner({ queryDb }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${active users}</h2>\n\n<p>\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>5. State access</h3>\n\n<p>When the runner context includes the app's state object, extructions can read\nfrom it directly:</p>\n\n<p>```js</p>\n\n<p>const doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${welcome}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Hello <b>\\${currentUser}</b>, you have \\${STATE.notifications.length} notifications\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>This is how the app passes its reactive state into extruction bodies.</p>\n\n<h3>6. Composition — multiple adapters</h3>\n\n<p>Adapters compose naturally since they're just JS functions:</p>\n\n<p>```js</p>\n\n<p>const doc = runner({ repoInfo, fetchJson, currentUser }, { evalFn: evalBody });</p>\n\n\n<p>```</p>"},{"id":"/root/children/302","type":"code","loc":{"start":53514,"end":53834,"line":{"s":1554,"e":1566,"code":["```","","## ${dashboard}","","\\`\\`\\`javascript","const user = currentUser","const repos = await fetchJson(\\`https://api.github.com/users/\\${user}/repos\\`)","const summary = repos.map(r => \\`- \\${r.name}: \\${repoInfo[r.name]?.description || \"unknown\"}\\`).join(\"\\n\")","return insert( \\`### \\${user}'s repos\\n\\n\\${summary}\\` )","\\`\\`\\`","","","```"]},"column":{"s":0,"e":3}},"dim":["","code.302"],"code":"```\n\n## ${dashboard}\n\n\\`\\`\\`javascript\nconst user = currentUser\nconst repos = await fetchJson(\\`https://api.github.com/users/\\${user}/repos\\`)\nconst summary = repos.map(r => \\`- \\${r.name}: \\${repoInfo[r.name]?.description || \"unknown\"}\\`).join(\"\\n\")\nreturn insert( \\`### \\${user}'s repos\\n\\n\\${summary}\\` )\n\\`\\`\\`\n\n\n```","symbName":"code","symbRange":[53836,54145],"symbRangeL":[null,1575],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```\n\n### How it works\n\nGiven this setup:\n\n```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```\n\nAn extruction body like:\n\n\n```\n## ${find stuff}\n\n\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```\n\n...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.\n\n### Naming rules\n\n- Keys must be **valid JS identifiers** (no hyphens, no leading digits)\n- Use **camelCase** — this is idiomatic for JS function names\n- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)\n- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`\n\n### Return protocol\n\nAdapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:\n\n- `return insert(value)` — the extruction produces output\n- `return undefined` or no return — extruction stays transparent\n- `throw error` — propagates to the consumer (or caught by `onExtructionError`)\n\nThis means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.\n\n### Adapter conventions\n\n1. **Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.\n\n2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.\n\n3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:\n\n\n```\n   ## ${search mdd}\n\n   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```\n\nThis is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.\n\n## Example adapters\n\n### 1. Simple lookup (sync)\n\n```js\n\nconst repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};\n\nconst doc = runner({ repoInfo }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${repo info}\n\n\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`**${r.stars}** stars — ${r.description}\\` )\n\\`\\`\\`\n\n\n```\n\n### 2. Search adapter\n\nAlready documented in [Search Adapter](#search-adapter). The pattern:\n\n```js\n\nimport { search } from \"./mdt/search-adapter.js\";\n\nconst doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);\n\n\n```\n\n```\n\n## ${results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- [\\${i.name}](${i.uri})\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\nThe key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.\n\n### 3. HTTP fetch\n\n```js\n\nconst fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};\n\nconst doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);\n\n\n```\n\n```\n\n## ${github stats}\n\n\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`\n\n\n```\n\nThe adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.\n\n### 4. Database query\n\n```js\n\nconst queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};\n\nconst doc = runner({ queryDb }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${active users}\n\n\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\n### 5. State access\n\nWhen the runner context includes the app's state object, extructions can read\nfrom it directly:\n\n```js\n\nconst doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${welcome}\n\n\\`\\`\\`javascript\nreturn insert( \\`Hello **\\${currentUser}**, you have \\${STATE.notifications.length} notifications\\` )\n\\`\\`\\`\n\n\n```\n\nThis is how the app passes its reactive state into extruction bodies.\n\n### 6. Composition — multiple adapters\n\nAdapters compose naturally since they're just JS functions:\n\n```js\n\nconst doc = runner({ repoInfo, fetchJson, currentUser }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${dashboard}\n\n\\`\\`\\`javascript\nconst user = currentUser\nconst repos = await fetchJson(\\`https://api.github.com/users/\\${user}/repos\\`)\nconst summary = repos.map(r => \\`- \\${r.name}: \\${repoInfo[r.name]?.description || \"unknown\"}\\`).join(\"\\n\")\nreturn insert( \\`### \\${user}'s repos\\n\\n\\${summary}\\` )\n\\`\\`\\`\n\n\n```\n\nHere `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is\na plain string — all coexist as named parameters.\n\n### 7. Using `_mdt_label` to drive adapters\n\nThe label (text between `${}`) is injected as `_mdt_label` automatically.\nThis lets a single adapter serve multiple extruction variants:","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>\n\n<h3>How it works</h3>\n\n<p>Given this setup:</p>\n\n<p>```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```</p>\n\n<p>An extruction body like:</p>\n\n\n<p>```</p><h2>${find stuff}</h2>\n\n<p>\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<p>...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.</p>\n\n<h3>Naming rules</h3>\n\n<ul><li>Keys must be <b>valid JS identifiers</b> (no hyphens, no leading digits)</li><li>Use <b>camelCase</b> — this is idiomatic for JS function names</li><li>Avoid the `<i>mdt</i>` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)</li><li>Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`</li></ul>\n\n<h3>Return protocol</h3>\n\n<p>Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:</p>\n\n<ul><li>`return insert(value)` — the extruction produces output</li><li>`return undefined` or no return — extruction stays transparent</li><li>`throw error` — propagates to the consumer (or caught by `onExtructionError`)</li></ul>\n\n<p>This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.</p>\n\n<h3>Adapter conventions</h3>\n\n<ol><li><b>Async by convention</b> — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.</li></ol>\n\n<ol><li><b>Error handling</b> — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.</li></ol>\n\n<ol><li><b>`_mdt_label`</b> — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:</li></ol>\n\n\n<p>```</p><h2>${search mdd}</h2>\n\n<p>   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```</p>\n\n<p>This is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.</p>\n\n<h2>Example adapters</h2>\n\n<h3>1. Simple lookup (sync)</h3>\n\n<p>```js</p>\n\n<p>const repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};</p>\n\n<p>const doc = runner({ repoInfo }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${repo info}</h2>\n\n<p>\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`<b>${r.stars}</b> stars — ${r.description}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>2. Search adapter</h3>\n\n<p>Already documented in <a href=\"#search-adapter\">Search Adapter</a>. The pattern:</p>\n\n<p>```js</p>\n\n<p>import { search } from \"./mdt/search-adapter.js\";</p>\n\n<p>const doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- <a href=\"${i.uri}\">\\${i.name}</a>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.</p>\n\n<h3>3. HTTP fetch</h3>\n\n<p>```js</p>\n\n<p>const fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};</p>\n\n<p>const doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${github stats}</h2>\n\n<p>\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.</p>\n\n<h3>4. Database query</h3>\n\n<p>```js</p>\n\n<p>const queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};</p>\n\n<p>const doc = runner({ queryDb }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${active users}</h2>\n\n<p>\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>5. State access</h3>\n\n<p>When the runner context includes the app's state object, extructions can read\nfrom it directly:</p>\n\n<p>```js</p>\n\n<p>const doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${welcome}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Hello <b>\\${currentUser}</b>, you have \\${STATE.notifications.length} notifications\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>This is how the app passes its reactive state into extruction bodies.</p>\n\n<h3>6. Composition — multiple adapters</h3>\n\n<p>Adapters compose naturally since they're just JS functions:</p>\n\n<p>```js</p>\n\n<p>const doc = runner({ repoInfo, fetchJson, currentUser }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${dashboard}</h2>\n\n<p>\\`\\`\\`javascript\nconst user = currentUser\nconst repos = await fetchJson(\\`https://api.github.com/users/\\${user}/repos\\`)\nconst summary = repos.map(r => \\`- \\${r.name}: \\${repoInfo[r.name]?.description || \"unknown\"}\\`).join(\"\\n\")\nreturn insert( \\`### \\${user}'s repos\\n\\n\\${summary}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>Here `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is\na plain string — all coexist as named parameters.</p>\n\n<h3>7. Using `_mdt_label` to drive adapters</h3>\n\n<p>The label (text between `${}`) is injected as `_mdt_label` automatically.\nThis lets a single adapter serve multiple extruction variants:</p>"},{"id":"/root/children/303","type":"paragraph","loc":{"start":53836,"end":53962,"line":{"s":1568,"e":1569,"code":["Here `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is","a plain string — all coexist as named parameters."]},"column":{"s":0,"e":49}},"dim":["","paragraph.303"],"code":"Here `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is\na plain string — all coexist as named parameters."},{"id":"/root/children/303/children/0","type":"text","loc":{"start":53836,"end":53841,"line":{"s":1568,"e":1568,"code":["Here `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is"]},"column":{"s":0,"e":5}},"dim":["","paragraph.303","text.0"],"code":"Here "},{"id":"/root/children/303/children/1","type":"inlineCode","loc":{"start":53841,"end":53851,"line":{"s":1568,"e":1568,"code":["Here `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is"]},"column":{"s":5,"e":15}},"dim":["","paragraph.303","inlineCode.1"],"code":"`repoInfo`"},{"id":"/root/children/303/children/2","type":"text","loc":{"start":53851,"end":53870,"line":{"s":1568,"e":1568,"code":["Here `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is"]},"column":{"s":15,"e":34}},"dim":["","paragraph.303","text.2"],"code":" is a sync lookup, "},{"id":"/root/children/303/children/3","type":"inlineCode","loc":{"start":53870,"end":53881,"line":{"s":1568,"e":1568,"code":["Here `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is"]},"column":{"s":34,"e":45}},"dim":["","paragraph.303","inlineCode.3"],"code":"`fetchJson`"},{"id":"/root/children/303/children/4","type":"text","loc":{"start":53881,"end":53896,"line":{"s":1568,"e":1568,"code":["Here `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is"]},"column":{"s":45,"e":60}},"dim":["","paragraph.303","text.4"],"code":" is async, and "},{"id":"/root/children/303/children/5","type":"inlineCode","loc":{"start":53896,"end":53909,"line":{"s":1568,"e":1568,"code":["Here `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is"]},"column":{"s":60,"e":73}},"dim":["","paragraph.303","inlineCode.5"],"code":"`currentUser`"},{"id":"/root/children/303/children/6","type":"text","loc":{"start":53909,"end":53962,"line":{"s":1568,"e":1569,"code":["Here `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is","a plain string — all coexist as named parameters."]},"column":{"s":73,"e":49}},"dim":["","paragraph.303","text.6"],"code":" is\na plain string — all coexist as named parameters."},{"id":"/root/children/304","type":"heading","loc":{"start":53964,"end":54007,"line":{"s":1571,"e":1571,"code":["### 7. Using `_mdt_label` to drive adapters"]},"column":{"s":0,"e":43}},"dim":["","heading.304"],"code":"### 7. Using `_mdt_label` to drive adapters","symbName":"heading","symbRange":[54009,54805],"symbRangeL":[1571,1616],"outerCode":"\nThe label (text between `${}`) is injected as `_mdt_label` automatically.\nThis lets a single adapter serve multiple extruction variants:\n\n```\n\n## ${fetch todos}\n\n\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/todos\"))\n\\`\\`\\`\n\n## ${fetch users}\n\n\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/users\") )\n\\`\\`\\`\n\n\n```\n\nWithout hardcoding the path in each body — although in this case you'd still\nneed to map the label to the path. A more practical use:\n\n```\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n\n## ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.name).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\nThe same `search` adapter is called with different labels.","outerHtml":"\n<p>The label (text between `${}`) is injected as `_mdt_label` automatically.\nThis lets a single adapter serve multiple extruction variants:</p>\n\n<p>```</p>\n\n<h2>${fetch todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/todos\"))\n\\`\\`\\`</p>\n\n<h2>${fetch users}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/users\") )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>Without hardcoding the path in each body — although in this case you'd still\nneed to map the label to the path. A more practical use:</p>\n\n<p>```</p>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>${search js}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.name).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The same `search` adapter is called with different labels.</p>"},{"id":"/root/children/304/children/0","type":"text","loc":{"start":53968,"end":53977,"line":{"s":1571,"e":1571,"code":["### 7. Using `_mdt_label` to drive adapters"]},"column":{"s":4,"e":13}},"dim":["","heading.304","text.0"],"code":"7. Using "},{"id":"/root/children/304/children/1","type":"inlineCode","loc":{"start":53977,"end":53989,"line":{"s":1571,"e":1571,"code":["### 7. Using `_mdt_label` to drive adapters"]},"column":{"s":13,"e":25}},"dim":["","heading.304","inlineCode.1"],"code":"`_mdt_label`"},{"id":"/root/children/304/children/2","type":"text","loc":{"start":53989,"end":54007,"line":{"s":1571,"e":1571,"code":["### 7. Using `_mdt_label` to drive adapters"]},"column":{"s":25,"e":43}},"dim":["","heading.304","text.2"],"code":" to drive adapters"},{"id":"/root/children/305","type":"paragraph","loc":{"start":54009,"end":54145,"line":{"s":1573,"e":1574,"code":["The label (text between `${}`) is injected as `_mdt_label` automatically.","This lets a single adapter serve multiple extruction variants:"]},"column":{"s":0,"e":62}},"dim":["","paragraph.305"],"code":"The label (text between `${}`) is injected as `_mdt_label` automatically.\nThis lets a single adapter serve multiple extruction variants:"},{"id":"/root/children/305/children/0","type":"text","loc":{"start":54009,"end":54033,"line":{"s":1573,"e":1573,"code":["The label (text between `${}`) is injected as `_mdt_label` automatically."]},"column":{"s":0,"e":24}},"dim":["","paragraph.305","text.0"],"code":"The label (text between "},{"id":"/root/children/305/children/1","type":"inlineCode","loc":{"start":54033,"end":54038,"line":{"s":1573,"e":1573,"code":["The label (text between `${}`) is injected as `_mdt_label` automatically."]},"column":{"s":24,"e":29}},"dim":["","paragraph.305","inlineCode.1"],"code":"`${}`"},{"id":"/root/children/305/children/2","type":"text","loc":{"start":54038,"end":54055,"line":{"s":1573,"e":1573,"code":["The label (text between `${}`) is injected as `_mdt_label` automatically."]},"column":{"s":29,"e":46}},"dim":["","paragraph.305","text.2"],"code":") is injected as "},{"id":"/root/children/305/children/3","type":"inlineCode","loc":{"start":54055,"end":54067,"line":{"s":1573,"e":1573,"code":["The label (text between `${}`) is injected as `_mdt_label` automatically."]},"column":{"s":46,"e":58}},"dim":["","paragraph.305","inlineCode.3"],"code":"`_mdt_label`"},{"id":"/root/children/305/children/4","type":"text","loc":{"start":54067,"end":54145,"line":{"s":1573,"e":1574,"code":["The label (text between `${}`) is injected as `_mdt_label` automatically.","This lets a single adapter serve multiple extruction variants:"]},"column":{"s":58,"e":62}},"dim":["","paragraph.305","text.4"],"code":" automatically.\nThis lets a single adapter serve multiple extruction variants:"},{"id":"/root/children/306","type":"code","loc":{"start":54147,"end":54337,"line":{"s":1576,"e":1591,"code":["```","","## ${fetch todos}","","\\`\\`\\`javascript","return insert( await fetchJson(\"/api/todos\"))","\\`\\`\\`","","## ${fetch users}","","\\`\\`\\`javascript","return insert( await fetchJson(\"/api/users\") )","\\`\\`\\`","","","```"]},"column":{"s":0,"e":3}},"dim":["","code.306"],"code":"```\n\n## ${fetch todos}\n\n\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/todos\"))\n\\`\\`\\`\n\n## ${fetch users}\n\n\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/users\") )\n\\`\\`\\`\n\n\n```","symbName":"code","symbRange":[54339,54472],"symbRangeL":[null,1595],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```\n\n### How it works\n\nGiven this setup:\n\n```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```\n\nAn extruction body like:\n\n\n```\n## ${find stuff}\n\n\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```\n\n...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.\n\n### Naming rules\n\n- Keys must be **valid JS identifiers** (no hyphens, no leading digits)\n- Use **camelCase** — this is idiomatic for JS function names\n- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)\n- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`\n\n### Return protocol\n\nAdapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:\n\n- `return insert(value)` — the extruction produces output\n- `return undefined` or no return — extruction stays transparent\n- `throw error` — propagates to the consumer (or caught by `onExtructionError`)\n\nThis means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.\n\n### Adapter conventions\n\n1. **Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.\n\n2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.\n\n3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:\n\n\n```\n   ## ${search mdd}\n\n   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```\n\nThis is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.\n\n## Example adapters\n\n### 1. Simple lookup (sync)\n\n```js\n\nconst repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};\n\nconst doc = runner({ repoInfo }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${repo info}\n\n\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`**${r.stars}** stars — ${r.description}\\` )\n\\`\\`\\`\n\n\n```\n\n### 2. Search adapter\n\nAlready documented in [Search Adapter](#search-adapter). The pattern:\n\n```js\n\nimport { search } from \"./mdt/search-adapter.js\";\n\nconst doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);\n\n\n```\n\n```\n\n## ${results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- [\\${i.name}](${i.uri})\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\nThe key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.\n\n### 3. HTTP fetch\n\n```js\n\nconst fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};\n\nconst doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);\n\n\n```\n\n```\n\n## ${github stats}\n\n\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`\n\n\n```\n\nThe adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.\n\n### 4. Database query\n\n```js\n\nconst queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};\n\nconst doc = runner({ queryDb }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${active users}\n\n\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\n### 5. State access\n\nWhen the runner context includes the app's state object, extructions can read\nfrom it directly:\n\n```js\n\nconst doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${welcome}\n\n\\`\\`\\`javascript\nreturn insert( \\`Hello **\\${currentUser}**, you have \\${STATE.notifications.length} notifications\\` )\n\\`\\`\\`\n\n\n```\n\nThis is how the app passes its reactive state into extruction bodies.\n\n### 6. Composition — multiple adapters\n\nAdapters compose naturally since they're just JS functions:\n\n```js\n\nconst doc = runner({ repoInfo, fetchJson, currentUser }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${dashboard}\n\n\\`\\`\\`javascript\nconst user = currentUser\nconst repos = await fetchJson(\\`https://api.github.com/users/\\${user}/repos\\`)\nconst summary = repos.map(r => \\`- \\${r.name}: \\${repoInfo[r.name]?.description || \"unknown\"}\\`).join(\"\\n\")\nreturn insert( \\`### \\${user}'s repos\\n\\n\\${summary}\\` )\n\\`\\`\\`\n\n\n```\n\nHere `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is\na plain string — all coexist as named parameters.\n\n### 7. Using `_mdt_label` to drive adapters\n\nThe label (text between `${}`) is injected as `_mdt_label` automatically.\nThis lets a single adapter serve multiple extruction variants:\n\n```\n\n## ${fetch todos}\n\n\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/todos\"))\n\\`\\`\\`\n\n## ${fetch users}\n\n\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/users\") )\n\\`\\`\\`\n\n\n```\n\nWithout hardcoding the path in each body — although in this case you'd still\nneed to map the label to the path. A more practical use:","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>\n\n<h3>How it works</h3>\n\n<p>Given this setup:</p>\n\n<p>```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```</p>\n\n<p>An extruction body like:</p>\n\n\n<p>```</p><h2>${find stuff}</h2>\n\n<p>\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<p>...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.</p>\n\n<h3>Naming rules</h3>\n\n<ul><li>Keys must be <b>valid JS identifiers</b> (no hyphens, no leading digits)</li><li>Use <b>camelCase</b> — this is idiomatic for JS function names</li><li>Avoid the `<i>mdt</i>` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)</li><li>Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`</li></ul>\n\n<h3>Return protocol</h3>\n\n<p>Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:</p>\n\n<ul><li>`return insert(value)` — the extruction produces output</li><li>`return undefined` or no return — extruction stays transparent</li><li>`throw error` — propagates to the consumer (or caught by `onExtructionError`)</li></ul>\n\n<p>This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.</p>\n\n<h3>Adapter conventions</h3>\n\n<ol><li><b>Async by convention</b> — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.</li></ol>\n\n<ol><li><b>Error handling</b> — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.</li></ol>\n\n<ol><li><b>`_mdt_label`</b> — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:</li></ol>\n\n\n<p>```</p><h2>${search mdd}</h2>\n\n<p>   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```</p>\n\n<p>This is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.</p>\n\n<h2>Example adapters</h2>\n\n<h3>1. Simple lookup (sync)</h3>\n\n<p>```js</p>\n\n<p>const repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};</p>\n\n<p>const doc = runner({ repoInfo }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${repo info}</h2>\n\n<p>\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`<b>${r.stars}</b> stars — ${r.description}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>2. Search adapter</h3>\n\n<p>Already documented in <a href=\"#search-adapter\">Search Adapter</a>. The pattern:</p>\n\n<p>```js</p>\n\n<p>import { search } from \"./mdt/search-adapter.js\";</p>\n\n<p>const doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- <a href=\"${i.uri}\">\\${i.name}</a>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.</p>\n\n<h3>3. HTTP fetch</h3>\n\n<p>```js</p>\n\n<p>const fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};</p>\n\n<p>const doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${github stats}</h2>\n\n<p>\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.</p>\n\n<h3>4. Database query</h3>\n\n<p>```js</p>\n\n<p>const queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};</p>\n\n<p>const doc = runner({ queryDb }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${active users}</h2>\n\n<p>\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>5. State access</h3>\n\n<p>When the runner context includes the app's state object, extructions can read\nfrom it directly:</p>\n\n<p>```js</p>\n\n<p>const doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${welcome}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Hello <b>\\${currentUser}</b>, you have \\${STATE.notifications.length} notifications\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>This is how the app passes its reactive state into extruction bodies.</p>\n\n<h3>6. Composition — multiple adapters</h3>\n\n<p>Adapters compose naturally since they're just JS functions:</p>\n\n<p>```js</p>\n\n<p>const doc = runner({ repoInfo, fetchJson, currentUser }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${dashboard}</h2>\n\n<p>\\`\\`\\`javascript\nconst user = currentUser\nconst repos = await fetchJson(\\`https://api.github.com/users/\\${user}/repos\\`)\nconst summary = repos.map(r => \\`- \\${r.name}: \\${repoInfo[r.name]?.description || \"unknown\"}\\`).join(\"\\n\")\nreturn insert( \\`### \\${user}'s repos\\n\\n\\${summary}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>Here `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is\na plain string — all coexist as named parameters.</p>\n\n<h3>7. Using `_mdt_label` to drive adapters</h3>\n\n<p>The label (text between `${}`) is injected as `_mdt_label` automatically.\nThis lets a single adapter serve multiple extruction variants:</p>\n\n<p>```</p>\n\n<h2>${fetch todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/todos\"))\n\\`\\`\\`</p>\n\n<h2>${fetch users}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/users\") )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>Without hardcoding the path in each body — although in this case you'd still\nneed to map the label to the path. A more practical use:</p>"},{"id":"/root/children/307","type":"paragraph","loc":{"start":54339,"end":54472,"line":{"s":1593,"e":1594,"code":["Without hardcoding the path in each body — although in this case you'd still","need to map the label to the path. A more practical use:"]},"column":{"s":0,"e":56}},"dim":["","paragraph.307"],"code":"Without hardcoding the path in each body — although in this case you'd still\nneed to map the label to the path. A more practical use:"},{"id":"/root/children/307/children/0","type":"text","loc":{"start":54339,"end":54472,"line":{"s":1593,"e":1594,"code":["Without hardcoding the path in each body — although in this case you'd still","need to map the label to the path. A more practical use:"]},"column":{"s":0,"e":56}},"dim":["","paragraph.307","text.0"],"code":"Without hardcoding the path in each body — although in this case you'd still\nneed to map the label to the path. A more practical use:"},{"id":"/root/children/308","type":"code","loc":{"start":54474,"end":54745,"line":{"s":1596,"e":1613,"code":["```","","## ${search mdd}","","\\`\\`\\`javascript","const items = await search(_mdt_label)","return insert( items.map(i => i.uri).join(\"\\n\"))","\\`\\`\\`","","## ${search js}","","\\`\\`\\`javascript","const items = await search(_mdt_label)","return insert( items.map(i => i.name).join(\"\\n\"))","\\`\\`\\`","","","```"]},"column":{"s":0,"e":3}},"dim":["","code.308"],"code":"```\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n\n## ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.name).join(\"\\n\"))\n\\`\\`\\`\n\n\n```","symbName":"code","symbRange":[54747,56222],"symbRangeL":[null,1641],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```\n\n### How it works\n\nGiven this setup:\n\n```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```\n\nAn extruction body like:\n\n\n```\n## ${find stuff}\n\n\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```\n\n...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.\n\n### Naming rules\n\n- Keys must be **valid JS identifiers** (no hyphens, no leading digits)\n- Use **camelCase** — this is idiomatic for JS function names\n- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)\n- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`\n\n### Return protocol\n\nAdapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:\n\n- `return insert(value)` — the extruction produces output\n- `return undefined` or no return — extruction stays transparent\n- `throw error` — propagates to the consumer (or caught by `onExtructionError`)\n\nThis means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.\n\n### Adapter conventions\n\n1. **Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.\n\n2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.\n\n3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:\n\n\n```\n   ## ${search mdd}\n\n   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```\n\nThis is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.\n\n## Example adapters\n\n### 1. Simple lookup (sync)\n\n```js\n\nconst repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};\n\nconst doc = runner({ repoInfo }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${repo info}\n\n\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`**${r.stars}** stars — ${r.description}\\` )\n\\`\\`\\`\n\n\n```\n\n### 2. Search adapter\n\nAlready documented in [Search Adapter](#search-adapter). The pattern:\n\n```js\n\nimport { search } from \"./mdt/search-adapter.js\";\n\nconst doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);\n\n\n```\n\n```\n\n## ${results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- [\\${i.name}](${i.uri})\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\nThe key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.\n\n### 3. HTTP fetch\n\n```js\n\nconst fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};\n\nconst doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);\n\n\n```\n\n```\n\n## ${github stats}\n\n\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`\n\n\n```\n\nThe adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.\n\n### 4. Database query\n\n```js\n\nconst queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};\n\nconst doc = runner({ queryDb }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${active users}\n\n\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\n### 5. State access\n\nWhen the runner context includes the app's state object, extructions can read\nfrom it directly:\n\n```js\n\nconst doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${welcome}\n\n\\`\\`\\`javascript\nreturn insert( \\`Hello **\\${currentUser}**, you have \\${STATE.notifications.length} notifications\\` )\n\\`\\`\\`\n\n\n```\n\nThis is how the app passes its reactive state into extruction bodies.\n\n### 6. Composition — multiple adapters\n\nAdapters compose naturally since they're just JS functions:\n\n```js\n\nconst doc = runner({ repoInfo, fetchJson, currentUser }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${dashboard}\n\n\\`\\`\\`javascript\nconst user = currentUser\nconst repos = await fetchJson(\\`https://api.github.com/users/\\${user}/repos\\`)\nconst summary = repos.map(r => \\`- \\${r.name}: \\${repoInfo[r.name]?.description || \"unknown\"}\\`).join(\"\\n\")\nreturn insert( \\`### \\${user}'s repos\\n\\n\\${summary}\\` )\n\\`\\`\\`\n\n\n```\n\nHere `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is\na plain string — all coexist as named parameters.\n\n### 7. Using `_mdt_label` to drive adapters\n\nThe label (text between `${}`) is injected as `_mdt_label` automatically.\nThis lets a single adapter serve multiple extruction variants:\n\n```\n\n## ${fetch todos}\n\n\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/todos\"))\n\\`\\`\\`\n\n## ${fetch users}\n\n\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/users\") )\n\\`\\`\\`\n\n\n```\n\nWithout hardcoding the path in each body — although in this case you'd still\nneed to map the label to the path. A more practical use:\n\n```\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n\n## ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.name).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\nThe same `search` adapter is called with different labels.\n\n### Key constraints\n\n| Constraint                                         | Why                                                         |\n| -------------------------------------------------- | ----------------------------------------------------------- |\n| Adapter names must be valid JS identifiers         | They become `AsyncFunction` parameter names                 |\n| Don't use `_mdt_` prefix                           | Reserved for library-injected context keys                  |\n| Adapters are evaluated fresh on each `evalFn` call | No caching — each expansion re-evaluates                    |\n| Return `{ insert }` to produce output              | Any other return keeps the extruction transparent           |\n| Context is spread, not just the adapter            | All context keys are available — plan namespace accordingly |\n\n### 6. E2E tests\n\nTest the full player-paper.js integration: `.mdt` file fetch → compile →\nrun with evalBody + adapters → rebuild clean md → render.\n\n---\n\n## Conversion tree — transclusion provenance\n\nWhen an mdt document transcludes content from source fragments (via extructions), the produced fragments have virtual trail positions in the generated document. To resolve these back to the real source fragments, the mdt runner produces a **conversion tree** alongside the generated text.\n\n### sourceFragment field\n\nEach `Fragment` now carries an optional `sourceFragment`:","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>\n\n<h3>How it works</h3>\n\n<p>Given this setup:</p>\n\n<p>```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```</p>\n\n<p>An extruction body like:</p>\n\n\n<p>```</p><h2>${find stuff}</h2>\n\n<p>\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<p>...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.</p>\n\n<h3>Naming rules</h3>\n\n<ul><li>Keys must be <b>valid JS identifiers</b> (no hyphens, no leading digits)</li><li>Use <b>camelCase</b> — this is idiomatic for JS function names</li><li>Avoid the `<i>mdt</i>` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)</li><li>Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`</li></ul>\n\n<h3>Return protocol</h3>\n\n<p>Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:</p>\n\n<ul><li>`return insert(value)` — the extruction produces output</li><li>`return undefined` or no return — extruction stays transparent</li><li>`throw error` — propagates to the consumer (or caught by `onExtructionError`)</li></ul>\n\n<p>This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.</p>\n\n<h3>Adapter conventions</h3>\n\n<ol><li><b>Async by convention</b> — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.</li></ol>\n\n<ol><li><b>Error handling</b> — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.</li></ol>\n\n<ol><li><b>`_mdt_label`</b> — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:</li></ol>\n\n\n<p>```</p><h2>${search mdd}</h2>\n\n<p>   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```</p>\n\n<p>This is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.</p>\n\n<h2>Example adapters</h2>\n\n<h3>1. Simple lookup (sync)</h3>\n\n<p>```js</p>\n\n<p>const repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};</p>\n\n<p>const doc = runner({ repoInfo }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${repo info}</h2>\n\n<p>\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`<b>${r.stars}</b> stars — ${r.description}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>2. Search adapter</h3>\n\n<p>Already documented in <a href=\"#search-adapter\">Search Adapter</a>. The pattern:</p>\n\n<p>```js</p>\n\n<p>import { search } from \"./mdt/search-adapter.js\";</p>\n\n<p>const doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- <a href=\"${i.uri}\">\\${i.name}</a>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.</p>\n\n<h3>3. HTTP fetch</h3>\n\n<p>```js</p>\n\n<p>const fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};</p>\n\n<p>const doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${github stats}</h2>\n\n<p>\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.</p>\n\n<h3>4. Database query</h3>\n\n<p>```js</p>\n\n<p>const queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};</p>\n\n<p>const doc = runner({ queryDb }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${active users}</h2>\n\n<p>\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>5. State access</h3>\n\n<p>When the runner context includes the app's state object, extructions can read\nfrom it directly:</p>\n\n<p>```js</p>\n\n<p>const doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${welcome}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Hello <b>\\${currentUser}</b>, you have \\${STATE.notifications.length} notifications\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>This is how the app passes its reactive state into extruction bodies.</p>\n\n<h3>6. Composition — multiple adapters</h3>\n\n<p>Adapters compose naturally since they're just JS functions:</p>\n\n<p>```js</p>\n\n<p>const doc = runner({ repoInfo, fetchJson, currentUser }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${dashboard}</h2>\n\n<p>\\`\\`\\`javascript\nconst user = currentUser\nconst repos = await fetchJson(\\`https://api.github.com/users/\\${user}/repos\\`)\nconst summary = repos.map(r => \\`- \\${r.name}: \\${repoInfo[r.name]?.description || \"unknown\"}\\`).join(\"\\n\")\nreturn insert( \\`### \\${user}'s repos\\n\\n\\${summary}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>Here `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is\na plain string — all coexist as named parameters.</p>\n\n<h3>7. Using `_mdt_label` to drive adapters</h3>\n\n<p>The label (text between `${}`) is injected as `_mdt_label` automatically.\nThis lets a single adapter serve multiple extruction variants:</p>\n\n<p>```</p>\n\n<h2>${fetch todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/todos\"))\n\\`\\`\\`</p>\n\n<h2>${fetch users}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/users\") )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>Without hardcoding the path in each body — although in this case you'd still\nneed to map the label to the path. A more practical use:</p>\n\n<p>```</p>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>${search js}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.name).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The same `search` adapter is called with different labels.</p>\n\n<h3>Key constraints</h3>\n\n<p>| Constraint                                         | Why                                                         |\n| -------------------------------------------------- | ----------------------------------------------------------- |\n| Adapter names must be valid JS identifiers         | They become `AsyncFunction` parameter names                 |\n| Don't use `<i>mdt</i>` prefix                           | Reserved for library-injected context keys                  |\n| Adapters are evaluated fresh on each `evalFn` call | No caching — each expansion re-evaluates                    |\n| Return `{ insert }` to produce output              | Any other return keeps the extruction transparent           |\n| Context is spread, not just the adapter            | All context keys are available — plan namespace accordingly |</p>\n\n<h3>6. E2E tests</h3>\n\n<p>Test the full player-paper.js integration: `.mdt` file fetch → compile →\nrun with evalBody + adapters → rebuild clean md → render.</p>\n\n<hr/>\n\n<h2>Conversion tree — transclusion provenance</h2>\n\n<p>When an mdt document transcludes content from source fragments (via extructions), the produced fragments have virtual trail positions in the generated document. To resolve these back to the real source fragments, the mdt runner produces a <b>conversion tree</b> alongside the generated text.</p>\n\n<h3>sourceFragment field</h3>\n\n<p>Each `Fragment` now carries an optional `sourceFragment`:</p>"},{"id":"/root/children/309","type":"paragraph","loc":{"start":54747,"end":54805,"line":{"s":1615,"e":1615,"code":["The same `search` adapter is called with different labels."]},"column":{"s":0,"e":58}},"dim":["","paragraph.309"],"code":"The same `search` adapter is called with different labels."},{"id":"/root/children/309/children/0","type":"text","loc":{"start":54747,"end":54756,"line":{"s":1615,"e":1615,"code":["The same `search` adapter is called with different labels."]},"column":{"s":0,"e":9}},"dim":["","paragraph.309","text.0"],"code":"The same "},{"id":"/root/children/309/children/1","type":"inlineCode","loc":{"start":54756,"end":54764,"line":{"s":1615,"e":1615,"code":["The same `search` adapter is called with different labels."]},"column":{"s":9,"e":17}},"dim":["","paragraph.309","inlineCode.1"],"code":"`search`"},{"id":"/root/children/309/children/2","type":"text","loc":{"start":54764,"end":54805,"line":{"s":1615,"e":1615,"code":["The same `search` adapter is called with different labels."]},"column":{"s":17,"e":58}},"dim":["","paragraph.309","text.2"],"code":" adapter is called with different labels."},{"id":"/root/children/310","type":"heading","loc":{"start":54807,"end":54826,"line":{"s":1617,"e":1617,"code":["### Key constraints"]},"column":{"s":0,"e":19}},"dim":["","heading.310"],"code":"### Key constraints","symbName":"heading","symbRange":[54828,55646],"symbRangeL":[1617,1626],"outerCode":"\n| Constraint                                         | Why                                                         |\n| -------------------------------------------------- | ----------------------------------------------------------- |\n| Adapter names must be valid JS identifiers         | They become `AsyncFunction` parameter names                 |\n| Don't use `_mdt_` prefix                           | Reserved for library-injected context keys                  |\n| Adapters are evaluated fresh on each `evalFn` call | No caching — each expansion re-evaluates                    |\n| Return `{ insert }` to produce output              | Any other return keeps the extruction transparent           |\n| Context is spread, not just the adapter            | All context keys are available — plan namespace accordingly |","outerHtml":"\n<p>| Constraint                                         | Why                                                         |\n| -------------------------------------------------- | ----------------------------------------------------------- |\n| Adapter names must be valid JS identifiers         | They become `AsyncFunction` parameter names                 |\n| Don't use `<i>mdt</i>` prefix                           | Reserved for library-injected context keys                  |\n| Adapters are evaluated fresh on each `evalFn` call | No caching — each expansion re-evaluates                    |\n| Return `{ insert }` to produce output              | Any other return keeps the extruction transparent           |\n| Context is spread, not just the adapter            | All context keys are available — plan namespace accordingly |</p>"},{"id":"/root/children/310/children/0","type":"text","loc":{"start":54811,"end":54826,"line":{"s":1617,"e":1617,"code":["### Key constraints"]},"column":{"s":4,"e":19}},"dim":["","heading.310","text.0"],"code":"Key constraints"},{"id":"/root/children/311","type":"paragraph","loc":{"start":54828,"end":55646,"line":{"s":1619,"e":1625,"code":["| Constraint                                         | Why                                                         |","| -------------------------------------------------- | ----------------------------------------------------------- |","| Adapter names must be valid JS identifiers         | They become `AsyncFunction` parameter names                 |","| Don't use `_mdt_` prefix                           | Reserved for library-injected context keys                  |","| Adapters are evaluated fresh on each `evalFn` call | No caching — each expansion re-evaluates                    |","| Return `{ insert }` to produce output              | Any other return keeps the extruction transparent           |","| Context is spread, not just the adapter            | All context keys are available — plan namespace accordingly |"]},"column":{"s":0,"e":116}},"dim":["","paragraph.311"],"code":"| Constraint                                         | Why                                                         |\n| -------------------------------------------------- | ----------------------------------------------------------- |\n| Adapter names must be valid JS identifiers         | They become `AsyncFunction` parameter names                 |\n| Don't use `_mdt_` prefix                           | Reserved for library-injected context keys                  |\n| Adapters are evaluated fresh on each `evalFn` call | No caching — each expansion re-evaluates                    |\n| Return `{ insert }` to produce output              | Any other return keeps the extruction transparent           |\n| Context is spread, not just the adapter            | All context keys are available — plan namespace accordingly |"},{"id":"/root/children/311/children/0","type":"text","loc":{"start":54828,"end":55129,"line":{"s":1619,"e":1621,"code":["| Constraint                                         | Why                                                         |","| -------------------------------------------------- | ----------------------------------------------------------- |","| Adapter names must be valid JS identifiers         | They become `AsyncFunction` parameter names                 |"]},"column":{"s":0,"e":67}},"dim":["","paragraph.311","text.0"],"code":"| Constraint                                         | Why                                                         |\n| -------------------------------------------------- | ----------------------------------------------------------- |\n| Adapter names must be valid JS identifiers         | They become "},{"id":"/root/children/311/children/1","type":"inlineCode","loc":{"start":55129,"end":55144,"line":{"s":1621,"e":1621,"code":["| Adapter names must be valid JS identifiers         | They become `AsyncFunction` parameter names                 |"]},"column":{"s":67,"e":82}},"dim":["","paragraph.311","inlineCode.1"],"code":"`AsyncFunction`"},{"id":"/root/children/311/children/2","type":"text","loc":{"start":55144,"end":55191,"line":{"s":1621,"e":1622,"code":["| Adapter names must be valid JS identifiers         | They become `AsyncFunction` parameter names                 |","| Don't use `_mdt_` prefix                           | Reserved for library-injected context keys                  |"]},"column":{"s":82,"e":12}},"dim":["","paragraph.311","text.2"],"code":" parameter names                 |\n| Don't use "},{"id":"/root/children/311/children/3","type":"inlineCode","loc":{"start":55191,"end":55198,"line":{"s":1622,"e":1622,"code":["| Don't use `_mdt_` prefix                           | Reserved for library-injected context keys                  |"]},"column":{"s":12,"e":19}},"dim":["","paragraph.311","inlineCode.3"],"code":"`_mdt_`"},{"id":"/root/children/311/children/4","type":"text","loc":{"start":55198,"end":55335,"line":{"s":1622,"e":1623,"code":["| Don't use `_mdt_` prefix                           | Reserved for library-injected context keys                  |","| Adapters are evaluated fresh on each `evalFn` call | No caching — each expansion re-evaluates                    |"]},"column":{"s":19,"e":39}},"dim":["","paragraph.311","text.4"],"code":" prefix                           | Reserved for library-injected context keys                  |\n| Adapters are evaluated fresh on each "},{"id":"/root/children/311/children/5","type":"inlineCode","loc":{"start":55335,"end":55343,"line":{"s":1623,"e":1623,"code":["| Adapters are evaluated fresh on each `evalFn` call | No caching — each expansion re-evaluates                    |"]},"column":{"s":39,"e":47}},"dim":["","paragraph.311","inlineCode.5"],"code":"`evalFn`"},{"id":"/root/children/311/children/6","type":"text","loc":{"start":55343,"end":55422,"line":{"s":1623,"e":1624,"code":["| Adapters are evaluated fresh on each `evalFn` call | No caching — each expansion re-evaluates                    |","| Return `{ insert }` to produce output              | Any other return keeps the extruction transparent           |"]},"column":{"s":47,"e":9}},"dim":["","paragraph.311","text.6"],"code":" call | No caching — each expansion re-evaluates                    |\n| Return "},{"id":"/root/children/311/children/7","type":"inlineCode","loc":{"start":55422,"end":55434,"line":{"s":1624,"e":1624,"code":["| Return `{ insert }` to produce output              | Any other return keeps the extruction transparent           |"]},"column":{"s":9,"e":21}},"dim":["","paragraph.311","inlineCode.7"],"code":"`{ insert }`"},{"id":"/root/children/311/children/8","type":"text","loc":{"start":55434,"end":55646,"line":{"s":1624,"e":1625,"code":["| Return `{ insert }` to produce output              | Any other return keeps the extruction transparent           |","| Context is spread, not just the adapter            | All context keys are available — plan namespace accordingly |"]},"column":{"s":21,"e":116}},"dim":["","paragraph.311","text.8"],"code":" to produce output              | Any other return keeps the extruction transparent           |\n| Context is spread, not just the adapter            | All context keys are available — plan namespace accordingly |"},{"id":"/root/children/312","type":"heading","loc":{"start":55648,"end":55664,"line":{"s":1627,"e":1627,"code":["### 6. E2E tests"]},"column":{"s":0,"e":16}},"dim":["","heading.312"],"code":"### 6. E2E tests","symbName":"heading","symbRange":[55666,55801],"symbRangeL":[1627,1633],"outerCode":"\nTest the full player-paper.js integration: `.mdt` file fetch → compile →\nrun with evalBody + adapters → rebuild clean md → render.\n\n---","outerHtml":"\n<p>Test the full player-paper.js integration: `.mdt` file fetch → compile →\nrun with evalBody + adapters → rebuild clean md → render.</p>\n\n<hr/>"},{"id":"/root/children/312/children/0","type":"text","loc":{"start":55652,"end":55664,"line":{"s":1627,"e":1627,"code":["### 6. E2E tests"]},"column":{"s":4,"e":16}},"dim":["","heading.312","text.0"],"code":"6. E2E tests"},{"id":"/root/children/313","type":"paragraph","loc":{"start":55666,"end":55796,"line":{"s":1629,"e":1630,"code":["Test the full player-paper.js integration: `.mdt` file fetch → compile →","run with evalBody + adapters → rebuild clean md → render."]},"column":{"s":0,"e":57}},"dim":["","paragraph.313"],"code":"Test the full player-paper.js integration: `.mdt` file fetch → compile →\nrun with evalBody + adapters → rebuild clean md → render."},{"id":"/root/children/313/children/0","type":"text","loc":{"start":55666,"end":55709,"line":{"s":1629,"e":1629,"code":["Test the full player-paper.js integration: `.mdt` file fetch → compile →"]},"column":{"s":0,"e":43}},"dim":["","paragraph.313","text.0"],"code":"Test the full player-paper.js integration: "},{"id":"/root/children/313/children/1","type":"inlineCode","loc":{"start":55709,"end":55715,"line":{"s":1629,"e":1629,"code":["Test the full player-paper.js integration: `.mdt` file fetch → compile →"]},"column":{"s":43,"e":49}},"dim":["","paragraph.313","inlineCode.1"],"code":"`.mdt`"},{"id":"/root/children/313/children/2","type":"text","loc":{"start":55715,"end":55796,"line":{"s":1629,"e":1630,"code":["Test the full player-paper.js integration: `.mdt` file fetch → compile →","run with evalBody + adapters → rebuild clean md → render."]},"column":{"s":49,"e":57}},"dim":["","paragraph.313","text.2"],"code":" file fetch → compile →\nrun with evalBody + adapters → rebuild clean md → render."},{"id":"/root/children/314","type":"thematicBreak","loc":{"start":55798,"end":55801,"line":{"s":1632,"e":1632,"code":["---"]},"column":{"s":0,"e":3}},"dim":["","thematicBreak.314"],"code":"---"},{"id":"/root/children/315","type":"heading","loc":{"start":55803,"end":55847,"line":{"s":1634,"e":1634,"code":["## Conversion tree — transclusion provenance"]},"column":{"s":0,"e":44}},"dim":["","heading.315"],"code":"## Conversion tree — transclusion provenance","symbName":"heading","symbRange":[55849,56137],"symbRangeL":[1634,1637],"outerCode":"\nWhen an mdt document transcludes content from source fragments (via extructions), the produced fragments have virtual trail positions in the generated document. To resolve these back to the real source fragments, the mdt runner produces a **conversion tree** alongside the generated text.","outerHtml":"\n<p>When an mdt document transcludes content from source fragments (via extructions), the produced fragments have virtual trail positions in the generated document. To resolve these back to the real source fragments, the mdt runner produces a <b>conversion tree</b> alongside the generated text.</p>"},{"id":"/root/children/315/children/0","type":"text","loc":{"start":55806,"end":55847,"line":{"s":1634,"e":1634,"code":["## Conversion tree — transclusion provenance"]},"column":{"s":3,"e":44}},"dim":["","heading.315","text.0"],"code":"Conversion tree — transclusion provenance"},{"id":"/root/children/316","type":"paragraph","loc":{"start":55849,"end":56137,"line":{"s":1636,"e":1636,"code":["When an mdt document transcludes content from source fragments (via extructions), the produced fragments have virtual trail positions in the generated document. To resolve these back to the real source fragments, the mdt runner produces a **conversion tree** alongside the generated text."]},"column":{"s":0,"e":288}},"dim":["","paragraph.316"],"code":"When an mdt document transcludes content from source fragments (via extructions), the produced fragments have virtual trail positions in the generated document. To resolve these back to the real source fragments, the mdt runner produces a **conversion tree** alongside the generated text."},{"id":"/root/children/316/children/0","type":"text","loc":{"start":55849,"end":56088,"line":{"s":1636,"e":1636,"code":["When an mdt document transcludes content from source fragments (via extructions), the produced fragments have virtual trail positions in the generated document. To resolve these back to the real source fragments, the mdt runner produces a **conversion tree** alongside the generated text."]},"column":{"s":0,"e":239}},"dim":["","paragraph.316","text.0"],"code":"When an mdt document transcludes content from source fragments (via extructions), the produced fragments have virtual trail positions in the generated document. To resolve these back to the real source fragments, the mdt runner produces a "},{"id":"/root/children/316/children/1","type":"strong","loc":{"start":56088,"end":56107,"line":{"s":1636,"e":1636,"code":["When an mdt document transcludes content from source fragments (via extructions), the produced fragments have virtual trail positions in the generated document. To resolve these back to the real source fragments, the mdt runner produces a **conversion tree** alongside the generated text."]},"column":{"s":239,"e":258}},"dim":["","paragraph.316","strong.1"],"code":"**conversion tree**"},{"id":"/root/children/316/children/1/children/0","type":"text","loc":{"start":56090,"end":56105,"line":{"s":1636,"e":1636,"code":["When an mdt document transcludes content from source fragments (via extructions), the produced fragments have virtual trail positions in the generated document. To resolve these back to the real source fragments, the mdt runner produces a **conversion tree** alongside the generated text."]},"column":{"s":241,"e":256}},"dim":["","paragraph.316","strong.1","text.0"],"code":"conversion tree"},{"id":"/root/children/316/children/2","type":"text","loc":{"start":56107,"end":56137,"line":{"s":1636,"e":1636,"code":["When an mdt document transcludes content from source fragments (via extructions), the produced fragments have virtual trail positions in the generated document. To resolve these back to the real source fragments, the mdt runner produces a **conversion tree** alongside the generated text."]},"column":{"s":258,"e":288}},"dim":["","paragraph.316","text.2"],"code":" alongside the generated text."},{"id":"/root/children/317","type":"heading","loc":{"start":56139,"end":56163,"line":{"s":1638,"e":1638,"code":["### sourceFragment field"]},"column":{"s":0,"e":24}},"dim":["","heading.317"],"code":"### sourceFragment field","symbName":"heading","symbRange":[56165,56599],"symbRangeL":[1638,1657],"outerCode":"\nEach `Fragment` now carries an optional `sourceFragment`:\n\n```js\n\n{\ntrail: \"a/x\",\nheading: \"## <!-- ... -->\",\nbody: \"hello\",\nsourceFragment: null | { fn: \"paper/real.mdd\", refId: \"paper/real.mdd::real/heading\" }\n}\n\n\n```\n\n- `buildFragment()` — regular headings: `sourceFragment: null`\n- `buildInjectFragment()` — injected raw content: `sourceFragment: null`\n- `buildInsertFragment()` — extruction-produced fragments: reads `cmd.source`","outerHtml":"\n<p>Each `Fragment` now carries an optional `sourceFragment`:</p>\n\n<p>```js</p>\n\n<p>{\ntrail: \"a/x\",\nheading: \"## <!-- ... -->\",\nbody: \"hello\",\nsourceFragment: null | { fn: \"paper/real.mdd\", refId: \"paper/real.mdd::real/heading\" }\n}</p>\n\n\n<p>```</p>\n\n<ul><li>`buildFragment()` — regular headings: `sourceFragment: null`</li><li>`buildInjectFragment()` — injected raw content: `sourceFragment: null`</li><li>`buildInsertFragment()` — extruction-produced fragments: reads `cmd.source`</li></ul>"},{"id":"/root/children/317/children/0","type":"text","loc":{"start":56143,"end":56163,"line":{"s":1638,"e":1638,"code":["### sourceFragment field"]},"column":{"s":4,"e":24}},"dim":["","heading.317","text.0"],"code":"sourceFragment field"},{"id":"/root/children/318","type":"paragraph","loc":{"start":56165,"end":56222,"line":{"s":1640,"e":1640,"code":["Each `Fragment` now carries an optional `sourceFragment`:"]},"column":{"s":0,"e":57}},"dim":["","paragraph.318"],"code":"Each `Fragment` now carries an optional `sourceFragment`:"},{"id":"/root/children/318/children/0","type":"text","loc":{"start":56165,"end":56170,"line":{"s":1640,"e":1640,"code":["Each `Fragment` now carries an optional `sourceFragment`:"]},"column":{"s":0,"e":5}},"dim":["","paragraph.318","text.0"],"code":"Each "},{"id":"/root/children/318/children/1","type":"inlineCode","loc":{"start":56170,"end":56180,"line":{"s":1640,"e":1640,"code":["Each `Fragment` now carries an optional `sourceFragment`:"]},"column":{"s":5,"e":15}},"dim":["","paragraph.318","inlineCode.1"],"code":"`Fragment`"},{"id":"/root/children/318/children/2","type":"text","loc":{"start":56180,"end":56205,"line":{"s":1640,"e":1640,"code":["Each `Fragment` now carries an optional `sourceFragment`:"]},"column":{"s":15,"e":40}},"dim":["","paragraph.318","text.2"],"code":" now carries an optional "},{"id":"/root/children/318/children/3","type":"inlineCode","loc":{"start":56205,"end":56221,"line":{"s":1640,"e":1640,"code":["Each `Fragment` now carries an optional `sourceFragment`:"]},"column":{"s":40,"e":56}},"dim":["","paragraph.318","inlineCode.3"],"code":"`sourceFragment`"},{"id":"/root/children/318/children/4","type":"text","loc":{"start":56221,"end":56222,"line":{"s":1640,"e":1640,"code":["Each `Fragment` now carries an optional `sourceFragment`:"]},"column":{"s":56,"e":57}},"dim":["","paragraph.318","text.4"],"code":":"},{"id":"/root/children/319","type":"code","loc":{"start":56224,"end":56384,"line":{"s":1642,"e":1652,"code":["```js","","{","trail: \"a/x\",","heading: \"## <!-- ... -->\",","body: \"hello\",","sourceFragment: null | { fn: \"paper/real.mdd\", refId: \"paper/real.mdd::real/heading\" }","}","","","```"]},"column":{"s":0,"e":3}},"dim":["","code.319"],"code":"```js\n\n{\ntrail: \"a/x\",\nheading: \"## <!-- ... -->\",\nbody: \"hello\",\nsourceFragment: null | { fn: \"paper/real.mdd\", refId: \"paper/real.mdd::real/heading\" }\n}\n\n\n```","symbName":"code","symbRange":[56386,56706],"symbRangeL":[null,1661],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```\n\n### How it works\n\nGiven this setup:\n\n```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```\n\nAn extruction body like:\n\n\n```\n## ${find stuff}\n\n\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```\n\n...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.\n\n### Naming rules\n\n- Keys must be **valid JS identifiers** (no hyphens, no leading digits)\n- Use **camelCase** — this is idiomatic for JS function names\n- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)\n- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`\n\n### Return protocol\n\nAdapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:\n\n- `return insert(value)` — the extruction produces output\n- `return undefined` or no return — extruction stays transparent\n- `throw error` — propagates to the consumer (or caught by `onExtructionError`)\n\nThis means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.\n\n### Adapter conventions\n\n1. **Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.\n\n2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.\n\n3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:\n\n\n```\n   ## ${search mdd}\n\n   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```\n\nThis is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.\n\n## Example adapters\n\n### 1. Simple lookup (sync)\n\n```js\n\nconst repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};\n\nconst doc = runner({ repoInfo }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${repo info}\n\n\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`**${r.stars}** stars — ${r.description}\\` )\n\\`\\`\\`\n\n\n```\n\n### 2. Search adapter\n\nAlready documented in [Search Adapter](#search-adapter). The pattern:\n\n```js\n\nimport { search } from \"./mdt/search-adapter.js\";\n\nconst doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);\n\n\n```\n\n```\n\n## ${results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- [\\${i.name}](${i.uri})\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\nThe key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.\n\n### 3. HTTP fetch\n\n```js\n\nconst fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};\n\nconst doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);\n\n\n```\n\n```\n\n## ${github stats}\n\n\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`\n\n\n```\n\nThe adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.\n\n### 4. Database query\n\n```js\n\nconst queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};\n\nconst doc = runner({ queryDb }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${active users}\n\n\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\n### 5. State access\n\nWhen the runner context includes the app's state object, extructions can read\nfrom it directly:\n\n```js\n\nconst doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${welcome}\n\n\\`\\`\\`javascript\nreturn insert( \\`Hello **\\${currentUser}**, you have \\${STATE.notifications.length} notifications\\` )\n\\`\\`\\`\n\n\n```\n\nThis is how the app passes its reactive state into extruction bodies.\n\n### 6. Composition — multiple adapters\n\nAdapters compose naturally since they're just JS functions:\n\n```js\n\nconst doc = runner({ repoInfo, fetchJson, currentUser }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${dashboard}\n\n\\`\\`\\`javascript\nconst user = currentUser\nconst repos = await fetchJson(\\`https://api.github.com/users/\\${user}/repos\\`)\nconst summary = repos.map(r => \\`- \\${r.name}: \\${repoInfo[r.name]?.description || \"unknown\"}\\`).join(\"\\n\")\nreturn insert( \\`### \\${user}'s repos\\n\\n\\${summary}\\` )\n\\`\\`\\`\n\n\n```\n\nHere `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is\na plain string — all coexist as named parameters.\n\n### 7. Using `_mdt_label` to drive adapters\n\nThe label (text between `${}`) is injected as `_mdt_label` automatically.\nThis lets a single adapter serve multiple extruction variants:\n\n```\n\n## ${fetch todos}\n\n\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/todos\"))\n\\`\\`\\`\n\n## ${fetch users}\n\n\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/users\") )\n\\`\\`\\`\n\n\n```\n\nWithout hardcoding the path in each body — although in this case you'd still\nneed to map the label to the path. A more practical use:\n\n```\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n\n## ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.name).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\nThe same `search` adapter is called with different labels.\n\n### Key constraints\n\n| Constraint                                         | Why                                                         |\n| -------------------------------------------------- | ----------------------------------------------------------- |\n| Adapter names must be valid JS identifiers         | They become `AsyncFunction` parameter names                 |\n| Don't use `_mdt_` prefix                           | Reserved for library-injected context keys                  |\n| Adapters are evaluated fresh on each `evalFn` call | No caching — each expansion re-evaluates                    |\n| Return `{ insert }` to produce output              | Any other return keeps the extruction transparent           |\n| Context is spread, not just the adapter            | All context keys are available — plan namespace accordingly |\n\n### 6. E2E tests\n\nTest the full player-paper.js integration: `.mdt` file fetch → compile →\nrun with evalBody + adapters → rebuild clean md → render.\n\n---\n\n## Conversion tree — transclusion provenance\n\nWhen an mdt document transcludes content from source fragments (via extructions), the produced fragments have virtual trail positions in the generated document. To resolve these back to the real source fragments, the mdt runner produces a **conversion tree** alongside the generated text.\n\n### sourceFragment field\n\nEach `Fragment` now carries an optional `sourceFragment`:\n\n```js\n\n{\ntrail: \"a/x\",\nheading: \"## <!-- ... -->\",\nbody: \"hello\",\nsourceFragment: null | { fn: \"paper/real.mdd\", refId: \"paper/real.mdd::real/heading\" }\n}\n\n\n```\n\n- `buildFragment()` — regular headings: `sourceFragment: null`\n- `buildInjectFragment()` — injected raw content: `sourceFragment: null`\n- `buildInsertFragment()` — extruction-produced fragments: reads `cmd.source`\n\n### Extruction protocol — insert() extended\n\n`insert()` accepts an optional second argument `{ source }`:","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>\n\n<h3>How it works</h3>\n\n<p>Given this setup:</p>\n\n<p>```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```</p>\n\n<p>An extruction body like:</p>\n\n\n<p>```</p><h2>${find stuff}</h2>\n\n<p>\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<p>...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.</p>\n\n<h3>Naming rules</h3>\n\n<ul><li>Keys must be <b>valid JS identifiers</b> (no hyphens, no leading digits)</li><li>Use <b>camelCase</b> — this is idiomatic for JS function names</li><li>Avoid the `<i>mdt</i>` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)</li><li>Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`</li></ul>\n\n<h3>Return protocol</h3>\n\n<p>Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:</p>\n\n<ul><li>`return insert(value)` — the extruction produces output</li><li>`return undefined` or no return — extruction stays transparent</li><li>`throw error` — propagates to the consumer (or caught by `onExtructionError`)</li></ul>\n\n<p>This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.</p>\n\n<h3>Adapter conventions</h3>\n\n<ol><li><b>Async by convention</b> — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.</li></ol>\n\n<ol><li><b>Error handling</b> — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.</li></ol>\n\n<ol><li><b>`_mdt_label`</b> — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:</li></ol>\n\n\n<p>```</p><h2>${search mdd}</h2>\n\n<p>   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```</p>\n\n<p>This is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.</p>\n\n<h2>Example adapters</h2>\n\n<h3>1. Simple lookup (sync)</h3>\n\n<p>```js</p>\n\n<p>const repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};</p>\n\n<p>const doc = runner({ repoInfo }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${repo info}</h2>\n\n<p>\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`<b>${r.stars}</b> stars — ${r.description}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>2. Search adapter</h3>\n\n<p>Already documented in <a href=\"#search-adapter\">Search Adapter</a>. The pattern:</p>\n\n<p>```js</p>\n\n<p>import { search } from \"./mdt/search-adapter.js\";</p>\n\n<p>const doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- <a href=\"${i.uri}\">\\${i.name}</a>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.</p>\n\n<h3>3. HTTP fetch</h3>\n\n<p>```js</p>\n\n<p>const fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};</p>\n\n<p>const doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${github stats}</h2>\n\n<p>\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.</p>\n\n<h3>4. Database query</h3>\n\n<p>```js</p>\n\n<p>const queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};</p>\n\n<p>const doc = runner({ queryDb }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${active users}</h2>\n\n<p>\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>5. State access</h3>\n\n<p>When the runner context includes the app's state object, extructions can read\nfrom it directly:</p>\n\n<p>```js</p>\n\n<p>const doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${welcome}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Hello <b>\\${currentUser}</b>, you have \\${STATE.notifications.length} notifications\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>This is how the app passes its reactive state into extruction bodies.</p>\n\n<h3>6. Composition — multiple adapters</h3>\n\n<p>Adapters compose naturally since they're just JS functions:</p>\n\n<p>```js</p>\n\n<p>const doc = runner({ repoInfo, fetchJson, currentUser }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${dashboard}</h2>\n\n<p>\\`\\`\\`javascript\nconst user = currentUser\nconst repos = await fetchJson(\\`https://api.github.com/users/\\${user}/repos\\`)\nconst summary = repos.map(r => \\`- \\${r.name}: \\${repoInfo[r.name]?.description || \"unknown\"}\\`).join(\"\\n\")\nreturn insert( \\`### \\${user}'s repos\\n\\n\\${summary}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>Here `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is\na plain string — all coexist as named parameters.</p>\n\n<h3>7. Using `_mdt_label` to drive adapters</h3>\n\n<p>The label (text between `${}`) is injected as `_mdt_label` automatically.\nThis lets a single adapter serve multiple extruction variants:</p>\n\n<p>```</p>\n\n<h2>${fetch todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/todos\"))\n\\`\\`\\`</p>\n\n<h2>${fetch users}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/users\") )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>Without hardcoding the path in each body — although in this case you'd still\nneed to map the label to the path. A more practical use:</p>\n\n<p>```</p>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>${search js}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.name).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The same `search` adapter is called with different labels.</p>\n\n<h3>Key constraints</h3>\n\n<p>| Constraint                                         | Why                                                         |\n| -------------------------------------------------- | ----------------------------------------------------------- |\n| Adapter names must be valid JS identifiers         | They become `AsyncFunction` parameter names                 |\n| Don't use `<i>mdt</i>` prefix                           | Reserved for library-injected context keys                  |\n| Adapters are evaluated fresh on each `evalFn` call | No caching — each expansion re-evaluates                    |\n| Return `{ insert }` to produce output              | Any other return keeps the extruction transparent           |\n| Context is spread, not just the adapter            | All context keys are available — plan namespace accordingly |</p>\n\n<h3>6. E2E tests</h3>\n\n<p>Test the full player-paper.js integration: `.mdt` file fetch → compile →\nrun with evalBody + adapters → rebuild clean md → render.</p>\n\n<hr/>\n\n<h2>Conversion tree — transclusion provenance</h2>\n\n<p>When an mdt document transcludes content from source fragments (via extructions), the produced fragments have virtual trail positions in the generated document. To resolve these back to the real source fragments, the mdt runner produces a <b>conversion tree</b> alongside the generated text.</p>\n\n<h3>sourceFragment field</h3>\n\n<p>Each `Fragment` now carries an optional `sourceFragment`:</p>\n\n<p>```js</p>\n\n<p>{\ntrail: \"a/x\",\nheading: \"## <!-- ... -->\",\nbody: \"hello\",\nsourceFragment: null | { fn: \"paper/real.mdd\", refId: \"paper/real.mdd::real/heading\" }\n}</p>\n\n\n<p>```</p>\n\n<ul><li>`buildFragment()` — regular headings: `sourceFragment: null`</li><li>`buildInjectFragment()` — injected raw content: `sourceFragment: null`</li><li>`buildInsertFragment()` — extruction-produced fragments: reads `cmd.source`</li></ul>\n\n<h3>Extruction protocol — insert() extended</h3>\n\n<p>`insert()` accepts an optional second argument `{ source }`:</p>"},{"id":"/root/children/320","type":"list","loc":{"start":56386,"end":56599,"line":{"s":1654,"e":1656,"code":["- `buildFragment()` — regular headings: `sourceFragment: null`","- `buildInjectFragment()` — injected raw content: `sourceFragment: null`","- `buildInsertFragment()` — extruction-produced fragments: reads `cmd.source`"]},"column":{"s":0,"e":77}},"dim":["","list.320"],"code":"- `buildFragment()` — regular headings: `sourceFragment: null`\n- `buildInjectFragment()` — injected raw content: `sourceFragment: null`\n- `buildInsertFragment()` — extruction-produced fragments: reads `cmd.source`","symbName":"list","symbRange":[56601,58199],"symbRangeL":[1654,1709],"outerCode":"- `buildInjectFragment()` — injected raw content: `sourceFragment: null`\n- `buildInsertFragment()` — extruction-produced fragments: reads `cmd.source`\n\n### Extruction protocol — insert() extended\n\n`insert()` accepts an optional second argument `{ source }`:\n\n```js\n\n// without source (existing behavior)\nreturn [insert(children)];\n\n// with source tag (new)\nreturn [\ninsert(children, {\nsource: { fn: \"paper/real.mdd\", refId: \"paper/real.mdd::real/heading\" },\n}),\n];\n\n\n```\n\nThe `source` object flows from the command → `buildInsertFragment` → Fragment → consumer's conversion tree.\n\n### Consumer collects conversion tree\n\nThe consumer iterates fragments and builds a `Map<trail, sourceFragment>`:\n\n```js\n\nconst conversionTree = new Map();\nfor await (const frag of doc) {\nif (frag.sourceFragment) {\nconversionTree.set(frag.trail, frag.sourceFragment);\n}\n}\n\n\n```\n\n### Rendering uses conversion tree\n\n`renderFractalCollageOfPaper` accepts `{ conversionTree }` in opts. In `renderAfterEffect`, the rendering trail (semicolons) is converted to `/`-separated key and looked up in the tree. If found, the source `refId` is used as `data-fragmentRef` instead of the computed virtual refId.\n\n### Trail format conversion\n\n| Context                | Format        | Example                                   |\n| ---------------------- | ------------- | ----------------------------------------- |\n| mdt Fragment.trail     | `/`-separated | `\"a/x\"`                                   |\n| rendering data-trail   | `;`-separated | `\"a;x\"`                                   |\n| conversionTree key     | `/`-separated | `\"a/x\"`                                   |\n| buildFragmentRef input | `;`-separated | `buildFragmentRef(base, \"a;x\", sanitize)` |\n\n### Changed files","outerHtml":"<ul><li>`buildInjectFragment()` — injected raw content: `sourceFragment: null`</li><li>`buildInsertFragment()` — extruction-produced fragments: reads `cmd.source`</li></ul>\n\n<h3>Extruction protocol — insert() extended</h3>\n\n<p>`insert()` accepts an optional second argument `{ source }`:</p>\n\n<p>```js</p>\n\n<p>// without source (existing behavior)\nreturn [insert(children)];</p>\n\n<p>// with source tag (new)\nreturn [\ninsert(children, {\nsource: { fn: \"paper/real.mdd\", refId: \"paper/real.mdd::real/heading\" },\n}),\n];</p>\n\n\n<p>```</p>\n\n<p>The `source` object flows from the command → `buildInsertFragment` → Fragment → consumer's conversion tree.</p>\n\n<h3>Consumer collects conversion tree</h3>\n\n<p>The consumer iterates fragments and builds a `Map<trail, sourceFragment>`:</p>\n\n<p>```js</p>\n\n<p>const conversionTree = new Map();\nfor await (const frag of doc) {\nif (frag.sourceFragment) {\nconversionTree.set(frag.trail, frag.sourceFragment);\n}\n}</p>\n\n\n<p>```</p>\n\n<h3>Rendering uses conversion tree</h3>\n\n<p>`renderFractalCollageOfPaper` accepts `{ conversionTree }` in opts. In `renderAfterEffect`, the rendering trail (semicolons) is converted to `/`-separated key and looked up in the tree. If found, the source `refId` is used as `data-fragmentRef` instead of the computed virtual refId.</p>\n\n<h3>Trail format conversion</h3>\n\n<p>| Context                | Format        | Example                                   |\n| ---------------------- | ------------- | ----------------------------------------- |\n| mdt Fragment.trail     | `/`-separated | `\"a/x\"`                                   |\n| rendering data-trail   | `;`-separated | `\"a;x\"`                                   |\n| conversionTree key     | `/`-separated | `\"a/x\"`                                   |\n| buildFragmentRef input | `;`-separated | `buildFragmentRef(base, \"a;x\", sanitize)` |</p>\n\n<h3>Changed files</h3>"},{"id":"/root/children/320/children/0","type":"listItem","loc":{"start":56386,"end":56448,"line":{"s":1654,"e":1654,"code":["- `buildFragment()` — regular headings: `sourceFragment: null`"]},"column":{"s":0,"e":62}},"dim":["","list.320","listItem.0"],"code":"- `buildFragment()` — regular headings: `sourceFragment: null`"},{"id":"/root/children/320/children/0/children/0","type":"paragraph","loc":{"start":56388,"end":56448,"line":{"s":1654,"e":1654,"code":["- `buildFragment()` — regular headings: `sourceFragment: null`"]},"column":{"s":2,"e":62}},"dim":["","list.320","listItem.0","paragraph.0"],"code":"`buildFragment()` — regular headings: `sourceFragment: null`"},{"id":"/root/children/320/children/0/children/0/children/0","type":"inlineCode","loc":{"start":56388,"end":56405,"line":{"s":1654,"e":1654,"code":["- `buildFragment()` — regular headings: `sourceFragment: null`"]},"column":{"s":2,"e":19}},"dim":["","list.320","listItem.0","paragraph.0","inlineCode.0"],"code":"`buildFragment()`"},{"id":"/root/children/320/children/0/children/0/children/1","type":"text","loc":{"start":56405,"end":56426,"line":{"s":1654,"e":1654,"code":["- `buildFragment()` — regular headings: `sourceFragment: null`"]},"column":{"s":19,"e":40}},"dim":["","list.320","listItem.0","paragraph.0","text.1"],"code":" — regular headings: "},{"id":"/root/children/320/children/0/children/0/children/2","type":"inlineCode","loc":{"start":56426,"end":56448,"line":{"s":1654,"e":1654,"code":["- `buildFragment()` — regular headings: `sourceFragment: null`"]},"column":{"s":40,"e":62}},"dim":["","list.320","listItem.0","paragraph.0","inlineCode.2"],"code":"`sourceFragment: null`"},{"id":"/root/children/320/children/1","type":"listItem","loc":{"start":56449,"end":56521,"line":{"s":1655,"e":1655,"code":["- `buildInjectFragment()` — injected raw content: `sourceFragment: null`"]},"column":{"s":0,"e":72}},"dim":["","list.320","listItem.1"],"code":"- `buildInjectFragment()` — injected raw content: `sourceFragment: null`"},{"id":"/root/children/320/children/1/children/0","type":"paragraph","loc":{"start":56451,"end":56521,"line":{"s":1655,"e":1655,"code":["- `buildInjectFragment()` — injected raw content: `sourceFragment: null`"]},"column":{"s":2,"e":72}},"dim":["","list.320","listItem.1","paragraph.0"],"code":"`buildInjectFragment()` — injected raw content: `sourceFragment: null`"},{"id":"/root/children/320/children/1/children/0/children/0","type":"inlineCode","loc":{"start":56451,"end":56474,"line":{"s":1655,"e":1655,"code":["- `buildInjectFragment()` — injected raw content: `sourceFragment: null`"]},"column":{"s":2,"e":25}},"dim":["","list.320","listItem.1","paragraph.0","inlineCode.0"],"code":"`buildInjectFragment()`"},{"id":"/root/children/320/children/1/children/0/children/1","type":"text","loc":{"start":56474,"end":56499,"line":{"s":1655,"e":1655,"code":["- `buildInjectFragment()` — injected raw content: `sourceFragment: null`"]},"column":{"s":25,"e":50}},"dim":["","list.320","listItem.1","paragraph.0","text.1"],"code":" — injected raw content: "},{"id":"/root/children/320/children/1/children/0/children/2","type":"inlineCode","loc":{"start":56499,"end":56521,"line":{"s":1655,"e":1655,"code":["- `buildInjectFragment()` — injected raw content: `sourceFragment: null`"]},"column":{"s":50,"e":72}},"dim":["","list.320","listItem.1","paragraph.0","inlineCode.2"],"code":"`sourceFragment: null`"},{"id":"/root/children/320/children/2","type":"listItem","loc":{"start":56522,"end":56599,"line":{"s":1656,"e":1656,"code":["- `buildInsertFragment()` — extruction-produced fragments: reads `cmd.source`"]},"column":{"s":0,"e":77}},"dim":["","list.320","listItem.2"],"code":"- `buildInsertFragment()` — extruction-produced fragments: reads `cmd.source`"},{"id":"/root/children/320/children/2/children/0","type":"paragraph","loc":{"start":56524,"end":56599,"line":{"s":1656,"e":1656,"code":["- `buildInsertFragment()` — extruction-produced fragments: reads `cmd.source`"]},"column":{"s":2,"e":77}},"dim":["","list.320","listItem.2","paragraph.0"],"code":"`buildInsertFragment()` — extruction-produced fragments: reads `cmd.source`"},{"id":"/root/children/320/children/2/children/0/children/0","type":"inlineCode","loc":{"start":56524,"end":56547,"line":{"s":1656,"e":1656,"code":["- `buildInsertFragment()` — extruction-produced fragments: reads `cmd.source`"]},"column":{"s":2,"e":25}},"dim":["","list.320","listItem.2","paragraph.0","inlineCode.0"],"code":"`buildInsertFragment()`"},{"id":"/root/children/320/children/2/children/0/children/1","type":"text","loc":{"start":56547,"end":56587,"line":{"s":1656,"e":1656,"code":["- `buildInsertFragment()` — extruction-produced fragments: reads `cmd.source`"]},"column":{"s":25,"e":65}},"dim":["","list.320","listItem.2","paragraph.0","text.1"],"code":" — extruction-produced fragments: reads "},{"id":"/root/children/320/children/2/children/0/children/2","type":"inlineCode","loc":{"start":56587,"end":56599,"line":{"s":1656,"e":1656,"code":["- `buildInsertFragment()` — extruction-produced fragments: reads `cmd.source`"]},"column":{"s":65,"e":77}},"dim":["","list.320","listItem.2","paragraph.0","inlineCode.2"],"code":"`cmd.source`"},{"id":"/root/children/321","type":"heading","loc":{"start":56601,"end":56644,"line":{"s":1658,"e":1658,"code":["### Extruction protocol — insert() extended"]},"column":{"s":0,"e":43}},"dim":["","heading.321"],"code":"### Extruction protocol — insert() extended","symbName":"heading","symbRange":[56646,57028],"symbRangeL":[1658,1678],"outerCode":"\n`insert()` accepts an optional second argument `{ source }`:\n\n```js\n\n// without source (existing behavior)\nreturn [insert(children)];\n\n// with source tag (new)\nreturn [\ninsert(children, {\nsource: { fn: \"paper/real.mdd\", refId: \"paper/real.mdd::real/heading\" },\n}),\n];\n\n\n```\n\nThe `source` object flows from the command → `buildInsertFragment` → Fragment → consumer's conversion tree.","outerHtml":"\n<p>`insert()` accepts an optional second argument `{ source }`:</p>\n\n<p>```js</p>\n\n<p>// without source (existing behavior)\nreturn [insert(children)];</p>\n\n<p>// with source tag (new)\nreturn [\ninsert(children, {\nsource: { fn: \"paper/real.mdd\", refId: \"paper/real.mdd::real/heading\" },\n}),\n];</p>\n\n\n<p>```</p>\n\n<p>The `source` object flows from the command → `buildInsertFragment` → Fragment → consumer's conversion tree.</p>"},{"id":"/root/children/321/children/0","type":"text","loc":{"start":56605,"end":56644,"line":{"s":1658,"e":1658,"code":["### Extruction protocol — insert() extended"]},"column":{"s":4,"e":43}},"dim":["","heading.321","text.0"],"code":"Extruction protocol — insert() extended"},{"id":"/root/children/322","type":"paragraph","loc":{"start":56646,"end":56706,"line":{"s":1660,"e":1660,"code":["`insert()` accepts an optional second argument `{ source }`:"]},"column":{"s":0,"e":60}},"dim":["","paragraph.322"],"code":"`insert()` accepts an optional second argument `{ source }`:"},{"id":"/root/children/322/children/0","type":"inlineCode","loc":{"start":56646,"end":56656,"line":{"s":1660,"e":1660,"code":["`insert()` accepts an optional second argument `{ source }`:"]},"column":{"s":0,"e":10}},"dim":["","paragraph.322","inlineCode.0"],"code":"`insert()`"},{"id":"/root/children/322/children/1","type":"text","loc":{"start":56656,"end":56693,"line":{"s":1660,"e":1660,"code":["`insert()` accepts an optional second argument `{ source }`:"]},"column":{"s":10,"e":47}},"dim":["","paragraph.322","text.1"],"code":" accepts an optional second argument "},{"id":"/root/children/322/children/2","type":"inlineCode","loc":{"start":56693,"end":56705,"line":{"s":1660,"e":1660,"code":["`insert()` accepts an optional second argument `{ source }`:"]},"column":{"s":47,"e":59}},"dim":["","paragraph.322","inlineCode.2"],"code":"`{ source }`"},{"id":"/root/children/322/children/3","type":"text","loc":{"start":56705,"end":56706,"line":{"s":1660,"e":1660,"code":["`insert()` accepts an optional second argument `{ source }`:"]},"column":{"s":59,"e":60}},"dim":["","paragraph.322","text.3"],"code":":"},{"id":"/root/children/323","type":"code","loc":{"start":56708,"end":56919,"line":{"s":1662,"e":1675,"code":["```js","","// without source (existing behavior)","return [insert(children)];","","// with source tag (new)","return [","insert(children, {","source: { fn: \"paper/real.mdd\", refId: \"paper/real.mdd::real/heading\" },","}),","];","","","```"]},"column":{"s":0,"e":3}},"dim":["","code.323"],"code":"```js\n\n// without source (existing behavior)\nreturn [insert(children)];\n\n// with source tag (new)\nreturn [\ninsert(children, {\nsource: { fn: \"paper/real.mdd\", refId: \"paper/real.mdd::real/heading\" },\n}),\n];\n\n\n```","symbName":"code","symbRange":[56921,57143],"symbRangeL":[null,1682],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```\n\n### How it works\n\nGiven this setup:\n\n```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```\n\nAn extruction body like:\n\n\n```\n## ${find stuff}\n\n\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```\n\n...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.\n\n### Naming rules\n\n- Keys must be **valid JS identifiers** (no hyphens, no leading digits)\n- Use **camelCase** — this is idiomatic for JS function names\n- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)\n- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`\n\n### Return protocol\n\nAdapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:\n\n- `return insert(value)` — the extruction produces output\n- `return undefined` or no return — extruction stays transparent\n- `throw error` — propagates to the consumer (or caught by `onExtructionError`)\n\nThis means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.\n\n### Adapter conventions\n\n1. **Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.\n\n2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.\n\n3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:\n\n\n```\n   ## ${search mdd}\n\n   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```\n\nThis is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.\n\n## Example adapters\n\n### 1. Simple lookup (sync)\n\n```js\n\nconst repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};\n\nconst doc = runner({ repoInfo }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${repo info}\n\n\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`**${r.stars}** stars — ${r.description}\\` )\n\\`\\`\\`\n\n\n```\n\n### 2. Search adapter\n\nAlready documented in [Search Adapter](#search-adapter). The pattern:\n\n```js\n\nimport { search } from \"./mdt/search-adapter.js\";\n\nconst doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);\n\n\n```\n\n```\n\n## ${results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- [\\${i.name}](${i.uri})\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\nThe key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.\n\n### 3. HTTP fetch\n\n```js\n\nconst fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};\n\nconst doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);\n\n\n```\n\n```\n\n## ${github stats}\n\n\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`\n\n\n```\n\nThe adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.\n\n### 4. Database query\n\n```js\n\nconst queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};\n\nconst doc = runner({ queryDb }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${active users}\n\n\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\n### 5. State access\n\nWhen the runner context includes the app's state object, extructions can read\nfrom it directly:\n\n```js\n\nconst doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${welcome}\n\n\\`\\`\\`javascript\nreturn insert( \\`Hello **\\${currentUser}**, you have \\${STATE.notifications.length} notifications\\` )\n\\`\\`\\`\n\n\n```\n\nThis is how the app passes its reactive state into extruction bodies.\n\n### 6. Composition — multiple adapters\n\nAdapters compose naturally since they're just JS functions:\n\n```js\n\nconst doc = runner({ repoInfo, fetchJson, currentUser }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${dashboard}\n\n\\`\\`\\`javascript\nconst user = currentUser\nconst repos = await fetchJson(\\`https://api.github.com/users/\\${user}/repos\\`)\nconst summary = repos.map(r => \\`- \\${r.name}: \\${repoInfo[r.name]?.description || \"unknown\"}\\`).join(\"\\n\")\nreturn insert( \\`### \\${user}'s repos\\n\\n\\${summary}\\` )\n\\`\\`\\`\n\n\n```\n\nHere `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is\na plain string — all coexist as named parameters.\n\n### 7. Using `_mdt_label` to drive adapters\n\nThe label (text between `${}`) is injected as `_mdt_label` automatically.\nThis lets a single adapter serve multiple extruction variants:\n\n```\n\n## ${fetch todos}\n\n\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/todos\"))\n\\`\\`\\`\n\n## ${fetch users}\n\n\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/users\") )\n\\`\\`\\`\n\n\n```\n\nWithout hardcoding the path in each body — although in this case you'd still\nneed to map the label to the path. A more practical use:\n\n```\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n\n## ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.name).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\nThe same `search` adapter is called with different labels.\n\n### Key constraints\n\n| Constraint                                         | Why                                                         |\n| -------------------------------------------------- | ----------------------------------------------------------- |\n| Adapter names must be valid JS identifiers         | They become `AsyncFunction` parameter names                 |\n| Don't use `_mdt_` prefix                           | Reserved for library-injected context keys                  |\n| Adapters are evaluated fresh on each `evalFn` call | No caching — each expansion re-evaluates                    |\n| Return `{ insert }` to produce output              | Any other return keeps the extruction transparent           |\n| Context is spread, not just the adapter            | All context keys are available — plan namespace accordingly |\n\n### 6. E2E tests\n\nTest the full player-paper.js integration: `.mdt` file fetch → compile →\nrun with evalBody + adapters → rebuild clean md → render.\n\n---\n\n## Conversion tree — transclusion provenance\n\nWhen an mdt document transcludes content from source fragments (via extructions), the produced fragments have virtual trail positions in the generated document. To resolve these back to the real source fragments, the mdt runner produces a **conversion tree** alongside the generated text.\n\n### sourceFragment field\n\nEach `Fragment` now carries an optional `sourceFragment`:\n\n```js\n\n{\ntrail: \"a/x\",\nheading: \"## <!-- ... -->\",\nbody: \"hello\",\nsourceFragment: null | { fn: \"paper/real.mdd\", refId: \"paper/real.mdd::real/heading\" }\n}\n\n\n```\n\n- `buildFragment()` — regular headings: `sourceFragment: null`\n- `buildInjectFragment()` — injected raw content: `sourceFragment: null`\n- `buildInsertFragment()` — extruction-produced fragments: reads `cmd.source`\n\n### Extruction protocol — insert() extended\n\n`insert()` accepts an optional second argument `{ source }`:\n\n```js\n\n// without source (existing behavior)\nreturn [insert(children)];\n\n// with source tag (new)\nreturn [\ninsert(children, {\nsource: { fn: \"paper/real.mdd\", refId: \"paper/real.mdd::real/heading\" },\n}),\n];\n\n\n```\n\nThe `source` object flows from the command → `buildInsertFragment` → Fragment → consumer's conversion tree.\n\n### Consumer collects conversion tree\n\nThe consumer iterates fragments and builds a `Map<trail, sourceFragment>`:","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>\n\n<h3>How it works</h3>\n\n<p>Given this setup:</p>\n\n<p>```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```</p>\n\n<p>An extruction body like:</p>\n\n\n<p>```</p><h2>${find stuff}</h2>\n\n<p>\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<p>...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.</p>\n\n<h3>Naming rules</h3>\n\n<ul><li>Keys must be <b>valid JS identifiers</b> (no hyphens, no leading digits)</li><li>Use <b>camelCase</b> — this is idiomatic for JS function names</li><li>Avoid the `<i>mdt</i>` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)</li><li>Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`</li></ul>\n\n<h3>Return protocol</h3>\n\n<p>Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:</p>\n\n<ul><li>`return insert(value)` — the extruction produces output</li><li>`return undefined` or no return — extruction stays transparent</li><li>`throw error` — propagates to the consumer (or caught by `onExtructionError`)</li></ul>\n\n<p>This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.</p>\n\n<h3>Adapter conventions</h3>\n\n<ol><li><b>Async by convention</b> — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.</li></ol>\n\n<ol><li><b>Error handling</b> — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.</li></ol>\n\n<ol><li><b>`_mdt_label`</b> — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:</li></ol>\n\n\n<p>```</p><h2>${search mdd}</h2>\n\n<p>   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```</p>\n\n<p>This is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.</p>\n\n<h2>Example adapters</h2>\n\n<h3>1. Simple lookup (sync)</h3>\n\n<p>```js</p>\n\n<p>const repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};</p>\n\n<p>const doc = runner({ repoInfo }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${repo info}</h2>\n\n<p>\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`<b>${r.stars}</b> stars — ${r.description}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>2. Search adapter</h3>\n\n<p>Already documented in <a href=\"#search-adapter\">Search Adapter</a>. The pattern:</p>\n\n<p>```js</p>\n\n<p>import { search } from \"./mdt/search-adapter.js\";</p>\n\n<p>const doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- <a href=\"${i.uri}\">\\${i.name}</a>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.</p>\n\n<h3>3. HTTP fetch</h3>\n\n<p>```js</p>\n\n<p>const fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};</p>\n\n<p>const doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${github stats}</h2>\n\n<p>\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.</p>\n\n<h3>4. Database query</h3>\n\n<p>```js</p>\n\n<p>const queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};</p>\n\n<p>const doc = runner({ queryDb }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${active users}</h2>\n\n<p>\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>5. State access</h3>\n\n<p>When the runner context includes the app's state object, extructions can read\nfrom it directly:</p>\n\n<p>```js</p>\n\n<p>const doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${welcome}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Hello <b>\\${currentUser}</b>, you have \\${STATE.notifications.length} notifications\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>This is how the app passes its reactive state into extruction bodies.</p>\n\n<h3>6. Composition — multiple adapters</h3>\n\n<p>Adapters compose naturally since they're just JS functions:</p>\n\n<p>```js</p>\n\n<p>const doc = runner({ repoInfo, fetchJson, currentUser }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${dashboard}</h2>\n\n<p>\\`\\`\\`javascript\nconst user = currentUser\nconst repos = await fetchJson(\\`https://api.github.com/users/\\${user}/repos\\`)\nconst summary = repos.map(r => \\`- \\${r.name}: \\${repoInfo[r.name]?.description || \"unknown\"}\\`).join(\"\\n\")\nreturn insert( \\`### \\${user}'s repos\\n\\n\\${summary}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>Here `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is\na plain string — all coexist as named parameters.</p>\n\n<h3>7. Using `_mdt_label` to drive adapters</h3>\n\n<p>The label (text between `${}`) is injected as `_mdt_label` automatically.\nThis lets a single adapter serve multiple extruction variants:</p>\n\n<p>```</p>\n\n<h2>${fetch todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/todos\"))\n\\`\\`\\`</p>\n\n<h2>${fetch users}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/users\") )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>Without hardcoding the path in each body — although in this case you'd still\nneed to map the label to the path. A more practical use:</p>\n\n<p>```</p>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>${search js}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.name).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The same `search` adapter is called with different labels.</p>\n\n<h3>Key constraints</h3>\n\n<p>| Constraint                                         | Why                                                         |\n| -------------------------------------------------- | ----------------------------------------------------------- |\n| Adapter names must be valid JS identifiers         | They become `AsyncFunction` parameter names                 |\n| Don't use `<i>mdt</i>` prefix                           | Reserved for library-injected context keys                  |\n| Adapters are evaluated fresh on each `evalFn` call | No caching — each expansion re-evaluates                    |\n| Return `{ insert }` to produce output              | Any other return keeps the extruction transparent           |\n| Context is spread, not just the adapter            | All context keys are available — plan namespace accordingly |</p>\n\n<h3>6. E2E tests</h3>\n\n<p>Test the full player-paper.js integration: `.mdt` file fetch → compile →\nrun with evalBody + adapters → rebuild clean md → render.</p>\n\n<hr/>\n\n<h2>Conversion tree — transclusion provenance</h2>\n\n<p>When an mdt document transcludes content from source fragments (via extructions), the produced fragments have virtual trail positions in the generated document. To resolve these back to the real source fragments, the mdt runner produces a <b>conversion tree</b> alongside the generated text.</p>\n\n<h3>sourceFragment field</h3>\n\n<p>Each `Fragment` now carries an optional `sourceFragment`:</p>\n\n<p>```js</p>\n\n<p>{\ntrail: \"a/x\",\nheading: \"## <!-- ... -->\",\nbody: \"hello\",\nsourceFragment: null | { fn: \"paper/real.mdd\", refId: \"paper/real.mdd::real/heading\" }\n}</p>\n\n\n<p>```</p>\n\n<ul><li>`buildFragment()` — regular headings: `sourceFragment: null`</li><li>`buildInjectFragment()` — injected raw content: `sourceFragment: null`</li><li>`buildInsertFragment()` — extruction-produced fragments: reads `cmd.source`</li></ul>\n\n<h3>Extruction protocol — insert() extended</h3>\n\n<p>`insert()` accepts an optional second argument `{ source }`:</p>\n\n<p>```js</p>\n\n<p>// without source (existing behavior)\nreturn [insert(children)];</p>\n\n<p>// with source tag (new)\nreturn [\ninsert(children, {\nsource: { fn: \"paper/real.mdd\", refId: \"paper/real.mdd::real/heading\" },\n}),\n];</p>\n\n\n<p>```</p>\n\n<p>The `source` object flows from the command → `buildInsertFragment` → Fragment → consumer's conversion tree.</p>\n\n<h3>Consumer collects conversion tree</h3>\n\n<p>The consumer iterates fragments and builds a `Map<trail, sourceFragment>`:</p>"},{"id":"/root/children/324","type":"paragraph","loc":{"start":56921,"end":57028,"line":{"s":1677,"e":1677,"code":["The `source` object flows from the command → `buildInsertFragment` → Fragment → consumer's conversion tree."]},"column":{"s":0,"e":107}},"dim":["","paragraph.324"],"code":"The `source` object flows from the command → `buildInsertFragment` → Fragment → consumer's conversion tree."},{"id":"/root/children/324/children/0","type":"text","loc":{"start":56921,"end":56925,"line":{"s":1677,"e":1677,"code":["The `source` object flows from the command → `buildInsertFragment` → Fragment → consumer's conversion tree."]},"column":{"s":0,"e":4}},"dim":["","paragraph.324","text.0"],"code":"The "},{"id":"/root/children/324/children/1","type":"inlineCode","loc":{"start":56925,"end":56933,"line":{"s":1677,"e":1677,"code":["The `source` object flows from the command → `buildInsertFragment` → Fragment → consumer's conversion tree."]},"column":{"s":4,"e":12}},"dim":["","paragraph.324","inlineCode.1"],"code":"`source`"},{"id":"/root/children/324/children/2","type":"text","loc":{"start":56933,"end":56966,"line":{"s":1677,"e":1677,"code":["The `source` object flows from the command → `buildInsertFragment` → Fragment → consumer's conversion tree."]},"column":{"s":12,"e":45}},"dim":["","paragraph.324","text.2"],"code":" object flows from the command → "},{"id":"/root/children/324/children/3","type":"inlineCode","loc":{"start":56966,"end":56987,"line":{"s":1677,"e":1677,"code":["The `source` object flows from the command → `buildInsertFragment` → Fragment → consumer's conversion tree."]},"column":{"s":45,"e":66}},"dim":["","paragraph.324","inlineCode.3"],"code":"`buildInsertFragment`"},{"id":"/root/children/324/children/4","type":"text","loc":{"start":56987,"end":57028,"line":{"s":1677,"e":1677,"code":["The `source` object flows from the command → `buildInsertFragment` → Fragment → consumer's conversion tree."]},"column":{"s":66,"e":107}},"dim":["","paragraph.324","text.4"],"code":" → Fragment → consumer's conversion tree."},{"id":"/root/children/325","type":"heading","loc":{"start":57030,"end":57067,"line":{"s":1679,"e":1679,"code":["### Consumer collects conversion tree"]},"column":{"s":0,"e":37}},"dim":["","heading.325"],"code":"### Consumer collects conversion tree","symbName":"heading","symbRange":[57069,57307],"symbRangeL":[1679,1694],"outerCode":"\nThe consumer iterates fragments and builds a `Map<trail, sourceFragment>`:\n\n```js\n\nconst conversionTree = new Map();\nfor await (const frag of doc) {\nif (frag.sourceFragment) {\nconversionTree.set(frag.trail, frag.sourceFragment);\n}\n}\n\n\n```","outerHtml":"\n<p>The consumer iterates fragments and builds a `Map<trail, sourceFragment>`:</p>\n\n<p>```js</p>\n\n<p>const conversionTree = new Map();\nfor await (const frag of doc) {\nif (frag.sourceFragment) {\nconversionTree.set(frag.trail, frag.sourceFragment);\n}\n}</p>\n\n\n<p>```</p>"},{"id":"/root/children/325/children/0","type":"text","loc":{"start":57034,"end":57067,"line":{"s":1679,"e":1679,"code":["### Consumer collects conversion tree"]},"column":{"s":4,"e":37}},"dim":["","heading.325","text.0"],"code":"Consumer collects conversion tree"},{"id":"/root/children/326","type":"paragraph","loc":{"start":57069,"end":57143,"line":{"s":1681,"e":1681,"code":["The consumer iterates fragments and builds a `Map<trail, sourceFragment>`:"]},"column":{"s":0,"e":74}},"dim":["","paragraph.326"],"code":"The consumer iterates fragments and builds a `Map<trail, sourceFragment>`:"},{"id":"/root/children/326/children/0","type":"text","loc":{"start":57069,"end":57114,"line":{"s":1681,"e":1681,"code":["The consumer iterates fragments and builds a `Map<trail, sourceFragment>`:"]},"column":{"s":0,"e":45}},"dim":["","paragraph.326","text.0"],"code":"The consumer iterates fragments and builds a "},{"id":"/root/children/326/children/1","type":"inlineCode","loc":{"start":57114,"end":57142,"line":{"s":1681,"e":1681,"code":["The consumer iterates fragments and builds a `Map<trail, sourceFragment>`:"]},"column":{"s":45,"e":73}},"dim":["","paragraph.326","inlineCode.1"],"code":"`Map<trail, sourceFragment>`"},{"id":"/root/children/326/children/2","type":"text","loc":{"start":57142,"end":57143,"line":{"s":1681,"e":1681,"code":["The consumer iterates fragments and builds a `Map<trail, sourceFragment>`:"]},"column":{"s":73,"e":74}},"dim":["","paragraph.326","text.2"],"code":":"},{"id":"/root/children/327","type":"code","loc":{"start":57145,"end":57307,"line":{"s":1683,"e":1693,"code":["```js","","const conversionTree = new Map();","for await (const frag of doc) {","if (frag.sourceFragment) {","conversionTree.set(frag.trail, frag.sourceFragment);","}","}","","","```"]},"column":{"s":0,"e":3}},"dim":["","code.327"],"code":"```js\n\nconst conversionTree = new Map();\nfor await (const frag of doc) {\nif (frag.sourceFragment) {\nconversionTree.set(frag.trail, frag.sourceFragment);\n}\n}\n\n\n```","symbName":"code","symbRange":[57309,58634],"symbRangeL":[null,1714],"outerCode":";{ engine:dot, rankdir:LR }\n\n# mdt\n\n- mdd transclusion\n- its runnable in nodejs\n- mq-declarative-actor can run it\n- sphere of fragments\n- dynamic paper, space\n- presented incrementally\n\n## transclusion\n\n- mdd transclusion is value.\n- using the [url in heading](fragment://./url-in-heading) institute, fragments can be referenced\n- this means a tertiary virtual mdd paper can be created, which opens opportunities:\n  - on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"\n  - on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal\n    - the referencing anchor derives information also by its position in the structure of the mdt markdown tree\n  - its similiar to [symmetric functional tree](<>)\n- see meta-data\n- see usage for [voting](fragment://voting)\n\n- valid mdd + m4\n  - at instruction point (= heading)\n    - insert select\n    - inject select\n- [mdt — Markdown Construction Pseudo-Code](#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode)\n- see TOT\n\n## ideas\n\n- an extruction can have the codeblock and also text\n- insert is fetching cached content of fragments\n- backend?\n  - final mdd will be produced?\n  - makes sense for space,\n\n# mdt — Markdown Construction Pseudo-Code Spec\n\nPure JavaScript library for a **markdown construction pseudo-code language**.\nMarkdown is the surface syntax.\n`# ${...}` headings are **extructions** — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.\n\nThe library follows a **compile / runner** split:\n\n- `compile(mdtText, { remark })` — static analysis, returns a `Runner`\n- The `Runner` is a function — call it with context and opts to\n  get a **Document**, which lazily yields expandable **Fragment** objects\n\nAll functions are **pure** — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.\n\n## The idea\n\n- sphere of fragments\n- dynamic markdown OLAP\n\nThe `# ${...}` construct is called an **extruction** — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.\n\nThe name evolved through several candidates during design:\n\n- **expansion** — suggests something that unfolds when activated\n- **diversion** — content that diverts from normal output flow\n- **fragment instruction** — a fragment that carries an instruction\n- **generator** — evokes generating content from the label\n- **extruction** — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"\n\nOther ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.\n\n## Goals\n\n- Markdown is the surface language\n- `# ${...}` headings are **extructions** — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval\n- **Lazy by default**: only process what the consumer pulls\n- **Pure functions throughout**: all dependencies are explicit arguments,\n  never closed-over imports\n\n## mdt as Markdown\n\nEvery `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.\n\n## compile()\n\n\n```\ncompile(mdtMd, { remark }) → Runner\n```\n\nSingle entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.\n\n\n```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'\n\nconst runner = compile(sourceMd, { remark })\n```\n\n**Compile-time errors** (thrown synchronously):\n\n- Unparseable markdown (remark parse failure)\n\nDuring compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.\n\n## Runner\n\n\n```\nrunner(context, opts?) → Document\n```\n\nThe runner is a function.\nCall it with context and options to get a **Document** — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.\n\n`opts` carries run-time dependencies:\n\n\n```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```\n\n`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.\n\n`opts.loadRefBody`:\n\n- `async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.\n- `targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.\n- App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.\n\n### Document\n\nA Document is both an **async iterable** (yields root-level Fragments) and\na **navigation hub** (find fragments by trail-id):\n\n\n```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```\n\n- `preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.\n- `find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands _only_ the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.\n- `children(trail)` — `find(trail)?.expand()`.\n\nA Document is **stateless and re-iterable** — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.\n\n### Usage — Iteration\n\n```js\nconst doc = runner({ user });\n\nfor await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"\n\n  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```\n\n### Usage — Trail navigation\n\n```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);\n\n// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}\n\n// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}\n\n// Preamble text before the first heading\nconsole.log(doc.preamble);\n```\n\n### Trail-id\n\nA **trail-id** is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:\n\n| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |\n\nThe trail is constructed with **the same stack algorithm** used by\n`getHeadingTrail` in the existing codebase:\n\n1. Walk all heading nodes depth-first (in document order)\n1. Maintain a stack of `{ level, sanitized }` entries\n1. When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading\n1. The trail is `stack.map(e => e.sanitized).join(\"/\")`\n\n**Extructions** (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.\n\nTraversal stops at the **first match** — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.\n\n### Usage — Extruction evaluation with adapters\n\nWhen `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:\n\n\n```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'\n\nconst md = `# ${greeting}\n\n\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello **\\${name}**\\`)\n\\`\\`\\`\n\n# Results\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\\\n\"))\n\\`\\`\\`\n\n## Total\n\n\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`\n\nconst search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })\n\nfor await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello **world**\"\n  // \"Results\" → normal heading, expanded below\n\n  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```\n\nThe extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.\n\n### Usage — Error recovery\n\nWhen an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:\n\n\n```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})\n\nfor await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```\n\nWithout the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.\n\n### Usage — Adapter with `_mdt_label`\n\nThe `_mdt_label` binding lets one adapter serve multiple extruction variants:\n\n\n```js\nconst md = `# ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\\\n\"))\n\\`\\`\\`\n\n# ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\\\n\"))\n\\`\\`\\`\n`\n\nconst search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}\n\nconst runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```\n\nThe same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.\n\n### Usage — State across extructions\n\nThe runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:\n\n```js\nconst md = `# ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`\n\n# ${first}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`\n\n# ${second}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;\n\nconst runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });\n\nfor await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```\n\n`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.\n\nCallers can pre-populate `mdtState` by passing it in the context:\n\n```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```\n\n\n```\n## ${header}\n\n\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```\n\nThis is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.\n\n**Why this works:** `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.\n\n### Phases\n\nThe runner materializes the document in phases:\n\n| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |\n\nNo phase happens until the consumer pulls.\n\n## Fragment\n\nA heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.\n\n\n```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```\n\n- `trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized\n- `heading` — the heading as markdown source (e.g. `\"## Details\"`)\n- `headingLevel` — depth (1 for `#`, 2 for `##`, etc.)\n- `body` — the immediate body text, **canonicalized**\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.\n- `hasChildren` — quick check without triggering expansion\n- `expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.\n- `toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.\n\n**AST source:** currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.\n\n### expand() traversal\n\n`expand()` walks the remark AST child heading nodes:\n\n1. Walk child nodes left-to-right in document order.\n1. When hitting a heading that\n   is **not** an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.\n1. When hitting an **extruction** heading → skip (inert, no output).\n1. **Other nodes** (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.\n\n**Body boundary rule:** content before the first child heading belongs to\nthe parent's `body`; content between child heading _N_ and\nthe next heading belongs to child _N_'s `body`.\n\n### Lazy guarantees\n\n- `expand()` does nothing until iterated\n- Iterating past the first few fragments doesn't process later fragments\n\n## Extruction\n\n\n```\n## ${label}\n\n\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```\n\nAn extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut **only code inside ` ```javascript ` code blocks** is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.\n\n| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |\n\nThe `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.\n\n### Transparency semantics\n\nExtructions are **fully transparent** — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are **promoted** to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.\n\nImplementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.\n\n## Error Handling\n\n**Compile-time** (thrown by `compile()`):\n\n- Unparseable markdown (remark parse failure)\n\n**Runtime** (caught by `onExtructionError` callback):\n\n- Syntax errors in extruction body JS\n- Runtime exceptions during extruction evaluation\n\nWhen an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:\n\n| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **Provided**                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as **transparent** (body skipped, children promoted). Iteration continues. |\n| **Not provided** (`null`/omitted) | Error **propagates** to the consumer's `for await` loop (backward compatible).                                                                                 |\n\nIn `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.\n\nAll errors include the source position (`node.position`) for debugging.\n\n## Open Questions\n\n### 1. What is `context` for?\n\n**Resolved:** `context` is **state** — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.\n\nThe runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.\n\n### 2. Extruction label semantics\n\n**Deferred.** `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.\n\n### 3. When will extruction bodies activate?\n\n**Resolved.** Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).\n\n### 4. Verbatim vs canonicalized body\n\n**Resolved.** `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.\n\n### 5. `hasChildren` and extructions\n\n**Resolved — extructions are fully transparent with child promotion.**\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are **promoted** to the parent's\n`expand()` output:\n\n- `hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.\n- Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.\n- Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.\n- `skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.\n- Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.\n- Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.\n\n## App Integration\n\nThe MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:\n\n1. **Dynamic imports**: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`\n2. **Fetch**: file content fetched via `ssss.fetchWithETag()` with ETag caching\n3. **Compile**: `compile(data, { remark })` → `Runner`\n4. **Run**: `runner(STATE)` → `Document` (STATE serves as context)\n5. **Rebuild clean markdown**: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered\n6. **Render**: clean markdown rendered via `ssss.renderMarkdown()`\n7. **Post-process**: heading tabindex, relative image URL resolution\n\nThe current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.\n\n## Extruction Evaluation\n\nExtruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.\n\n### evalBody\n\n`mdt/eval-body.js` exports the default evaluation function:\n\n\n```\nevalBody(bodyText, context) → Promise<any>\n```\n\nIt uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.\n\n```js\nimport { evalBody } from \"./mdt/eval-body.js\";\n\nconst doc = runner({ search, STATE }, { evalFn: evalBody });\n```\n\nInside an extruction body, any key from the context is directly accessible:\n\n\n```\n## ${the list}\n\n\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```\n\n### Extruction return value — `insert()` / `inject()` built-ins\n\nWhen `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):\n\n- **`insert(children)`** — pipe Fragment-like objects directly into the output\n- **`inject(text)`** — produce a single raw-body Fragment with no heading\n- **`children`** — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)\n\n#### `insert(children)`\n\nTakes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:\n\n\n```\n## ${search results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```\n\nPass a single fragment or an array — `insert()` handles both:\n\n```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```\n\n#### `inject(text)`\n\nTakes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** generated from live data.\")\n\\`\\`\\`\n```\n\nThe Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.\n\n#### `children` — recursively resolved child subtree\n\nThe `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n**not** included (that's the `bodyText` passed to `evalFn`).\n\nResolution is **recursive** — `children` is computed by walking the child\ntree and processing each node:\n\n| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| **Extruction** (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| **Extruction** (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| **Extruction** (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| **Extruction** (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| **Regular heading**                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |\n\nThis means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.\n\nA common pattern is to pipe children through `insert()`:\n\n\n```\n## ${list of todos}\n\n\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```\n\n`children` is an empty string `\"\"` when:\n\n- The extruction has no child headings\n- The extruction is at root level with no children\n\nNon-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.\n\n#### `insertRefsAsSubtree(items, opts?)`\n\nTurn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with **lazy body-fetch**:\n\n\n```\n## ${search fragments; do}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```\n\nEach item becomes ONE Fragment one level **below** the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).\n\n\n```\n## insertRefsAsSubtree      ← depth 2, visible parent\n### ${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)\n#### auth                   ← depth 4, one Fragment per item\n##### …transcluded body…    ← depth 5+, from loadRefBody\n```\n\nThis is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.\n\n**Item contract (minimum):**\n\n| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |\n\nItems missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.\n\nThe common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.\n\n**opts:**\n\n| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |\n\n**Runner opt required:** `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.\n\n#### `insertNljson(collection, opts?)`\n\nSerialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:\n\n\n```\n## ${rows}\n\n\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"a\":1}\n{\"b\":2}\n```\n\nA single non-array value is wrapped. This is a **raw passthrough** — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.\n\n#### `insertRefsAsList(items, opts?)`\n\nRender an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:\n\n\n```\n## ${links}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```\n- [auth](#/paper/todo.mdd/auth) {{\"platba\":{\"suma\":42}}}\n- [login](#/paper/a.mdd)\n- plain\n```\n\nLabels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.\n\n| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |\n\n#### `insertRefsAsNljson(items, optsOrFn?)`\n\nRender an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees **table-safe scalar cells**:\n\n\n```\n## ${table}\n\n\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```\n\n\n```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```\n\n`link` is an **HTML anchor** (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).\n\nEvery row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.\n\n**Second argument — object or function.** A bare function is shorthand for\n`{ extend: fn }`:\n\n\n```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```\n\n`extend(item, row)` receives the **raw** item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.\n\n| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |\n\n#### `buildUrl(content, mimeType?)`\n\nNot a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:\n\n\n```\n\\`\\`\\`javascript\nreturn [inject(`[download](${buildUrl(JSON.stringify(rows), \"application/json\")})`)]\n\\`\\`\\`\n```\n\n#### Mixed output\n\nReturn an array of calls to produce multiple items in sequence:\n\n\n```\n## ${mixed}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /* fragment shape */ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```\n\nEach item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.\n\n#### Return nothing\n\n- **Omit `return` or return `undefined`** — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).\n- **Return `null`** — the extruction is removed and its children are\n  **suppressed** (dropped entirely, not promoted).\n\n#### State still via `mdtState`\n\nThe `mdtState` object is mutated directly through property assignment, not\nthrough helpers:\n\n\n```\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`\n\n## ${count}\n\n\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```\n\n#### Adapters — `search`, `searchVotes`, `votesAsRefs`\n\nAdapters are **not** commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to _obtain_\nitems, which the `insert*` verbs then render. All three are `await`-ed.\n\n| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |\n\n`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by **prefix, not exact name**:\n\n| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:*' OR campaign GLOB 'b:*' )` |\n| `[]`         | none — returns `[]` without querying             |\n\nThis mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.\n\nRows come back as objects:\n\n\n```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```\n\n`score` is **not** selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:\n\n```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```\n\nVerified identical to the view's SQL expression across the real vote rows.\n\n`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.\n\nIt is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.\n\n**Example — list voted fragments:**\n\n\n```md\n## ${init}\n\n\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`\n\n### ${list}\n\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```\n\nBoth are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.\n\n#### Command contract — all verbs\n\n| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- [nomen](uri) {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | **N** (one per item) | heading-only; body fetched lazily in `expand()` |\n\n`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.\n\n**`insertRefsAsSubtree` is the structural odd one out.** Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment _per item_, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.\n\n**`source` tagging** (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.\n\n**Two dispatch sites** handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.\n\nUnder the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.\n\n**Example — injecting a preamble:**\n\n\n```\n## ${notice}\n\n\\`\\`\\`javascript\nreturn inject(\"> **Note:** this document is generated from live data.\")\n\\`\\`\\`\n```\n\nThis produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.\n\n**Implementation notes:**\n\n- `buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).\n- `normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.\n- `processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.\n- Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.\n- `inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.\n\n### hasChildren & extruction evaluation\n\nWhen `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).\n\n### Error behavior\n\n- **No evalFn** — extruction bodies are inert (silently dropped).\n- **evalFn provided, body has JS syntax error** — `SyntaxError` propagates.\n- **evalFn provided, runtime error** — error propagates from the evaluation.\n\nThe snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.\n\n### buildInsertFragment serialization\n\n`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:\n\n- **Array** — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`\n- **Object (non-array)** — `JSON.stringify`\n- **Primitive** — `String()`\n\nThis prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).\n\n### Probes\n\nTwo `console.log` probes are placed at the extruction result handling points:\n\n- `probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.\n- `probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.\n\nThese are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.\n\n## Search Adapter\n\nThe MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.\n\n### glassSearchRunAsync\n\n`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:\n\n\n```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```\n\nThe wrapper:\n\n1. Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)\n2. Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results\n3. Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`\n4. Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`\n5. Has a 5-second safety fallback for async sources\n\nReturns a **thenable** object — supports both event-based and Promise-based usage:\n\n```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));\n\n// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```\n\n### search() adapter\n\n`mdt/search-adapter.js` exports a thin convenience function:\n\n\n```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```\n\nReturns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.\n\n### Completion detection\n\nThe \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:\n\n1. **Sync sources** (files, map) push directly to `resultList` inside `searchInRepoJson`\n2. **Debounced SQLite sources** (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called\n3. **History source** arrives via `searchInHistory` → `proxy.addResultItems`\n\nThe wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.\n\n## Adapter Pattern\n\nAdapters are **functions injected into the runner context** that extruction\nbodies can call as if they were local variables. The mechanism is simple:\n\n1. The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data\n2. `evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function\n3. The extruction body can `await adapterName(...)` just like any JS function\n\n\n```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```\n\n### How it works\n\nGiven this setup:\n\n```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```\n\nAn extruction body like:\n\n\n```\n## ${find stuff}\n\n\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```\n\n...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.\n\n### Naming rules\n\n- Keys must be **valid JS identifiers** (no hyphens, no leading digits)\n- Use **camelCase** — this is idiomatic for JS function names\n- Avoid the `_mdt_` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)\n- Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`\n\n### Return protocol\n\nAdapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:\n\n- `return insert(value)` — the extruction produces output\n- `return undefined` or no return — extruction stays transparent\n- `throw error` — propagates to the consumer (or caught by `onExtructionError`)\n\nThis means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.\n\n### Adapter conventions\n\n1. **Async by convention** — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.\n\n2. **Error handling** — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.\n\n3. **`_mdt_label`** — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:\n\n\n```\n   ## ${search mdd}\n\n   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```\n\nThis is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.\n\n## Example adapters\n\n### 1. Simple lookup (sync)\n\n```js\n\nconst repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};\n\nconst doc = runner({ repoInfo }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${repo info}\n\n\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`**${r.stars}** stars — ${r.description}\\` )\n\\`\\`\\`\n\n\n```\n\n### 2. Search adapter\n\nAlready documented in [Search Adapter](#search-adapter). The pattern:\n\n```js\n\nimport { search } from \"./mdt/search-adapter.js\";\n\nconst doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);\n\n\n```\n\n```\n\n## ${results}\n\n\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- [\\${i.name}](${i.uri})\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\nThe key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.\n\n### 3. HTTP fetch\n\n```js\n\nconst fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};\n\nconst doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);\n\n\n```\n\n```\n\n## ${github stats}\n\n\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`\n\n\n```\n\nThe adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.\n\n### 4. Database query\n\n```js\n\nconst queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};\n\nconst doc = runner({ queryDb }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${active users}\n\n\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\n### 5. State access\n\nWhen the runner context includes the app's state object, extructions can read\nfrom it directly:\n\n```js\n\nconst doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${welcome}\n\n\\`\\`\\`javascript\nreturn insert( \\`Hello **\\${currentUser}**, you have \\${STATE.notifications.length} notifications\\` )\n\\`\\`\\`\n\n\n```\n\nThis is how the app passes its reactive state into extruction bodies.\n\n### 6. Composition — multiple adapters\n\nAdapters compose naturally since they're just JS functions:\n\n```js\n\nconst doc = runner({ repoInfo, fetchJson, currentUser }, { evalFn: evalBody });\n\n\n```\n\n```\n\n## ${dashboard}\n\n\\`\\`\\`javascript\nconst user = currentUser\nconst repos = await fetchJson(\\`https://api.github.com/users/\\${user}/repos\\`)\nconst summary = repos.map(r => \\`- \\${r.name}: \\${repoInfo[r.name]?.description || \"unknown\"}\\`).join(\"\\n\")\nreturn insert( \\`### \\${user}'s repos\\n\\n\\${summary}\\` )\n\\`\\`\\`\n\n\n```\n\nHere `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is\na plain string — all coexist as named parameters.\n\n### 7. Using `_mdt_label` to drive adapters\n\nThe label (text between `${}`) is injected as `_mdt_label` automatically.\nThis lets a single adapter serve multiple extruction variants:\n\n```\n\n## ${fetch todos}\n\n\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/todos\"))\n\\`\\`\\`\n\n## ${fetch users}\n\n\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/users\") )\n\\`\\`\\`\n\n\n```\n\nWithout hardcoding the path in each body — although in this case you'd still\nneed to map the label to the path. A more practical use:\n\n```\n\n## ${search mdd}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n\n## ${search js}\n\n\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.name).join(\"\\n\"))\n\\`\\`\\`\n\n\n```\n\nThe same `search` adapter is called with different labels.\n\n### Key constraints\n\n| Constraint                                         | Why                                                         |\n| -------------------------------------------------- | ----------------------------------------------------------- |\n| Adapter names must be valid JS identifiers         | They become `AsyncFunction` parameter names                 |\n| Don't use `_mdt_` prefix                           | Reserved for library-injected context keys                  |\n| Adapters are evaluated fresh on each `evalFn` call | No caching — each expansion re-evaluates                    |\n| Return `{ insert }` to produce output              | Any other return keeps the extruction transparent           |\n| Context is spread, not just the adapter            | All context keys are available — plan namespace accordingly |\n\n### 6. E2E tests\n\nTest the full player-paper.js integration: `.mdt` file fetch → compile →\nrun with evalBody + adapters → rebuild clean md → render.\n\n---\n\n## Conversion tree — transclusion provenance\n\nWhen an mdt document transcludes content from source fragments (via extructions), the produced fragments have virtual trail positions in the generated document. To resolve these back to the real source fragments, the mdt runner produces a **conversion tree** alongside the generated text.\n\n### sourceFragment field\n\nEach `Fragment` now carries an optional `sourceFragment`:\n\n```js\n\n{\ntrail: \"a/x\",\nheading: \"## <!-- ... -->\",\nbody: \"hello\",\nsourceFragment: null | { fn: \"paper/real.mdd\", refId: \"paper/real.mdd::real/heading\" }\n}\n\n\n```\n\n- `buildFragment()` — regular headings: `sourceFragment: null`\n- `buildInjectFragment()` — injected raw content: `sourceFragment: null`\n- `buildInsertFragment()` — extruction-produced fragments: reads `cmd.source`\n\n### Extruction protocol — insert() extended\n\n`insert()` accepts an optional second argument `{ source }`:\n\n```js\n\n// without source (existing behavior)\nreturn [insert(children)];\n\n// with source tag (new)\nreturn [\ninsert(children, {\nsource: { fn: \"paper/real.mdd\", refId: \"paper/real.mdd::real/heading\" },\n}),\n];\n\n\n```\n\nThe `source` object flows from the command → `buildInsertFragment` → Fragment → consumer's conversion tree.\n\n### Consumer collects conversion tree\n\nThe consumer iterates fragments and builds a `Map<trail, sourceFragment>`:\n\n```js\n\nconst conversionTree = new Map();\nfor await (const frag of doc) {\nif (frag.sourceFragment) {\nconversionTree.set(frag.trail, frag.sourceFragment);\n}\n}\n\n\n```\n\n### Rendering uses conversion tree\n\n`renderFractalCollageOfPaper` accepts `{ conversionTree }` in opts. In `renderAfterEffect`, the rendering trail (semicolons) is converted to `/`-separated key and looked up in the tree. If found, the source `refId` is used as `data-fragmentRef` instead of the computed virtual refId.\n\n### Trail format conversion\n\n| Context                | Format        | Example                                   |\n| ---------------------- | ------------- | ----------------------------------------- |\n| mdt Fragment.trail     | `/`-separated | `\"a/x\"`                                   |\n| rendering data-trail   | `;`-separated | `\"a;x\"`                                   |\n| conversionTree key     | `/`-separated | `\"a/x\"`                                   |\n| buildFragmentRef input | `;`-separated | `buildFragmentRef(base, \"a;x\", sanitize)` |\n\n### Changed files\n\n- `mdt/mdt.js` — insert() helper, buildInsertFragment/buildInjectFragment/buildFragment, processExtructionResult\n- `player/player-paper.js` — .mdt case collects conversionTree during fragment walk, passes to renderFractalCollageOfPaper\n- `player/player-utils.js` — renderFractalCollageOfPaper accepts conversionTree, resolves source refId\n- `mdt/mdt.test.mjs` — 5 new tests: sourceFragment presence/absence, conversionTree collection","outerHtml":"<p>;{ engine:dot, rankdir:LR }</p>\n\n<h1>mdt</h1>\n\n<ul><li>mdd transclusion</li><li>its runnable in nodejs</li><li>mq-declarative-actor can run it</li><li>sphere of fragments</li><li>dynamic paper, space</li><li>presented incrementally</li></ul>\n\n<h2>transclusion</h2>\n\n<ul><li>mdd transclusion is value.</li><li>using the <a href=\"fragment://./url-in-heading\">url in heading</a> institute, fragments can be referenced</li><li>this means a tertiary virtual mdd paper can be created, which opens opportunities:<ul><li>on render of the mdt, it can render the referenced fragments as needed; maybe add \"buttons\"</li><li>on the other hand: each fragment (anywhere) can have all mdt's (where its referenced) at disposal<ul><li>the referencing anchor derives information also by its position in the structure of the mdt markdown tree</li></ul></li><li>its similiar to [symmetric functional tree](<>)</li></ul></li><li>see meta-data</li><li>see usage for <a href=\"fragment://voting\">voting</a></li></ul>\n\n<ul><li>valid mdd + m4<ul><li>at instruction point (= heading)<ul><li>insert select</li><li>inject select</li></ul></li></ul></li><li><a href=\"#/paper/paper/mechanism/mdt/mdt.mdd::mdtMarkdownConstructionPseudoCode\">mdt — Markdown Construction Pseudo-Code</a></li><li>see TOT</li></ul>\n\n<h2>ideas</h2>\n\n<ul><li>an extruction can have the codeblock and also text</li><li>insert is fetching cached content of fragments</li><li>backend?<ul><li>final mdd will be produced?</li><li>makes sense for space,</li></ul></li></ul>\n\n<h1>mdt — Markdown Construction Pseudo-Code Spec</h1>\n\n<p>Pure JavaScript library for a <b>markdown construction pseudo-code language</b>.\nMarkdown is the surface syntax.\n`# ${...}` headings are <b>extructions</b> — labeled markers that\nproduce no output; bodies use ` ```javascript ` code blocks for eval.</p>\n\n<p>The library follows a <b>compile / runner</b> split:</p>\n\n<ul><li>`compile(mdtText, { remark })` — static analysis, returns a `Runner`</li><li>The `Runner` is a function — call it with context and opts to\n  get a <b>Document</b>, which lazily yields expandable <b>Fragment</b> objects</li></ul>\n\n<p>All functions are <b>pure</b> — no mutation of inputs, no side effects,\nno classes, all external dependencies passed as arguments.</p>\n\n<h2>The idea</h2>\n\n<ul><li>sphere of fragments</li><li>dynamic markdown OLAP</li></ul>\n\n<p>The `# ${...}` construct is called an <b>extruction</b> — a coined term for\na labeled heading marker that produces no output;\nthe body uses ` ```javascript ` code blocks for evaluation.</p>\n\n<p>The name evolved through several candidates during design:</p>\n\n<ul><li><b>expansion</b> — suggests something that unfolds when activated</li><li><b>diversion</b> — content that diverts from normal output flow</li><li><b>fragment instruction</b> — a fragment that carries an instruction</li><li><b>generator</b> — evokes generating content from the label</li><li><b>extruction</b> — chosen; portmanteau hinting at \"extract\" / \"execute\"\n  and \"construction\"</li></ul>\n\n<p>Other ideas considered: hatch, vault, pocket, slot, well, lens, scope,\nportal, embed, injection, graft, splice, yield, emit, render.</p>\n\n<h2>Goals</h2>\n\n<ul><li>Markdown is the surface language</li><li>`# ${...}` headings are <b>extructions</b> — labeled markers, filtered\n  from output; bodies use ` ```javascript ` code blocks for eval</li><li><b>Lazy by default</b>: only process what the consumer pulls</li><li><b>Pure functions throughout</b>: all dependencies are explicit arguments,\n  never closed-over imports</li></ul>\n\n<h2>mdt as Markdown</h2>\n\n<p>Every `.mdd` file is also valid `.md`.\nExtructions (`# ${label}`) render as ordinary visible headings.\nStandard markdown renderers see no special syntax — the mdt semantics are\ninvisible to them.</p>\n\n<h2>compile()</h2>\n\n\n<p>```\ncompile(mdtMd, { remark }) → Runner\n```</p>\n\n<p>Single entry point.\nTakes raw mdt markdown text and a remark instance (for `.parse()`).\nReturns a `Runner` — no evaluation happens yet.</p>\n\n\n<p>```\nimport { compile } from './mdt/mdt.js'\nimport { remark } from 'remark'</p>\n\n<p>const runner = compile(sourceMd, { remark })\n```</p>\n\n<p><b>Compile-time errors</b> (thrown synchronously):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p>During compilation, headings whose text starts with `${` are marked as\nextructions.\nThey are tracked separately but\nno transform is applied — the remark AST is kept as-is.</p>\n\n<h2>Runner</h2>\n\n\n<p>```\nrunner(context, opts?) → Document\n```</p>\n\n<p>The runner is a function.\nCall it with context and options to get a <b>Document</b> — the entry point for\nnavigating the document tree.\nNo processing happens until you pull from the iterable or call navigate.</p>\n\n<p>`opts` carries run-time dependencies:</p>\n\n\n<p>```\nopts = {\n  sanitizeName: (str) => str.toLowerCase().replace(/\\W+/g, '-').replace(/^-+|-+$/g, '')\n}\n```</p>\n\n<p>`sanitizeName` defaults to the function shown (lowercase, non-word chars to\n`-`, leading/trailing dashes trimmed). Callers can override.</p>\n\n<p>`opts.loadRefBody`:</p>\n\n<ul><li>`async (item, targetDepth) → string` — fetches the body markdown for\n  one `insertRefsAsSubtree` item. Called lazily, only when a Fragment's `expand()`\n  is iterated by the consumer.</li><li>`targetDepth` is the heading depth at which the Fragment's root\n  heading is emitted; the returned body must have its own root heading\n  stripped and its nested subheadings shifted so root+1 lands at\n  `targetDepth+1`, root+2 at `targetDepth+2`, etc.</li><li>App integration: compose existing `loadFragment(...)` +\n  `relevelFragment(text, targetDepth - 1)` (bare import from\n  `player-utils.js`, not `ssss.relevelFragment`) + a regex strip of the\n  root heading. `relevelFragment(text, N)` puts the source root at\n  depth `N+1`, so passing `targetDepth - 1` puts the root at\n  `targetDepth` — after the root-strip, the source's root+1 headings\n  are what's left, correctly landing at `targetDepth+1`.</li></ul>\n\n<h3>Document</h3>\n\n<p>A Document is both an <b>async iterable</b> (yields root-level Fragments) and\na <b>navigation hub</b> (find fragments by trail-id):</p>\n\n\n<p>```\ndoc[Symbol.asyncIterator]() → AsyncIterable<Fragment>\ndoc.find(trail)              → Fragment | undefined\ndoc.children(trail)          → AsyncIterable<Fragment>\ndoc.preamble                 → string\n```</p>\n\n<ul><li>`preamble` — any text in the source that appears before the first heading.\n  Empty string if there is none.</li><li>`find(trail)` — walks lazily along the matching prefix only.\n  At each level it compares the next trail segment against child sanitized\n  names and expands <i>only</i> the matching child, abandoning the rest.\n  Cost is O(path length) expansions, not O(document).\n  Returns `undefined` if no match.</li><li>`children(trail)` — `find(trail)?.expand()`.</li></ul>\n\n<p>A Document is <b>stateless and re-iterable</b> — each call to\nthe runner produces a fresh Document, and each iteration re-derives from\nthe compiled tree.</p>\n\n<h3>Usage — Iteration</h3>\n\n<p>```js\nconst doc = runner({ user });</p>\n\n<p>for await (const section of doc) {\n  // section.heading → \"# Chapter 1\"\n  // section.body → \"Some text...\"\n  // section.toString() → \"# Chapter 1\\n\\nSome text...\"</p>\n\n<p>  for await (const child of section.expand()) {\n    // child.heading → \"## Section 1.1\"\n    // child.headingLevel → 2\n    // child.body → \"Details...\"\n  }\n}\n```</p>\n\n<h3>Usage — Trail navigation</h3>\n\n<p>```js\nconst doc = runner(\n  { user },\n  {\n    sanitizeName: (s) => s.toLowerCase().replace(/\\W+/g, \"-\"),\n  },\n);</p>\n\n<p>// Find a heading by trail-id\nconst section = doc.find(\"getting-started/installation\");\nfor await (const step of section.expand()) {\n  // immediate children of ## Installation\n}</p>\n\n<p>// Or shortcut: get children directly\nfor await (const step of doc.children(\"getting-started/installation\")) {\n  // same result\n}</p>\n\n<p>// Preamble text before the first heading\nconsole.log(doc.preamble);\n```</p>\n\n<h3>Trail-id</h3>\n\n<p>A <b>trail-id</b> is a `/`-separated path of sanitized heading names that\nuniquely identifies a heading in the document hierarchy:</p>\n\n<p>| Heading             | Trail                                  |\n| ------------------- | -------------------------------------- |\n| `# Getting Started` | `\"getting-started\"`                    |\n| `## Installation`   | `\"getting-started/installation\"`       |\n| `### Linux`         | `\"getting-started/installation/linux\"` |\n| `### macOS`         | `\"getting-started/installation/macos\"` |\n| `## Usage`          | `\"getting-started/usage\"`              |</p>\n\n<p>The trail is constructed with <b>the same stack algorithm</b> used by\n`getHeadingTrail` in the existing codebase:</p>\n\n<ol><li>Walk all heading nodes depth-first (in document order)</li><li>Maintain a stack of `{ level, sanitized }` entries</li><li>When a heading at level N is encountered, pop all stack entries where\n   `level >= N`, then push this heading</li><li>The trail is `stack.map(e => e.sanitized).join(\"/\")`</li></ol>\n\n<p><b>Extructions</b> (`# ${label}`) are skipped by\nthe trail algorithm — they produce no output and don't contribute to the stack.\nA `## Details` after an extruction `## ${sidebar}`\nat the same level gets trail `\"intro/details\"`, not `\"intro/sidebar/details\"`.</p>\n\n<p>Traversal stops at the <b>first match</b> — `find()` and `children()`\nreturn the section at the exact trail without pre-processing the entire\ndocument. Fragments past the match are not materialized.</p>\n\n<h3>Usage — Extruction evaluation with adapters</h3>\n\n<p>When `evalFn` is provided, extruction bodies run as JavaScript and can\nproduce output via the `insert` protocol:</p>\n\n\n<p>```js\nimport { compile } from './mdt/mdt.js'\nimport { evalBody } from './mdt/eval-body.js'\nimport { remark } from 'remark'</p>\n\n<p>const md = `# ${greeting}</p>\n\n<p>\\`\\`\\`javascript\nconst name = _mdt_label\nreturn insert(\\`Hello <b>\\${name}</b>\\`)\n\\`\\`\\`</p>\n\n<h1>Results</h1>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>Total</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert(String(total))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => [\n  { name: \"file1\", uri: \"#/paper/file1\" },\n  { name: \"file2\", uri: \"#/paper/file2\" },\n]\nconst total = 42</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search, total }, { evalFn: evalBody })</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString())\n  // \"${greeting}\" → \"<!-- greeting -->\\n\\nHello <b>world</b>\"\n  // \"Results\" → normal heading, expanded below</p>\n\n<p>  for await (const child of section.expand()) {\n    // \"${search mdd}\" → \"#/paper/file1\\n#/paper/file2\"\n    // \"Total\" → \"42\"\n  }\n}\n```</p>\n\n<p>The extruction body `return insert(value)` yields one or more Fragment-like\nobjects directly into the output. Any `await`-able function in context is an\nadapter — `search`, `total`, and `_mdt_label` all coexist as named bindings.</p>\n\n<h3>Usage — Error recovery</h3>\n\n<p>When an extruction body throws, `onExtructionError` lets you log and skip\ninstead of crashing the iteration:</p>\n\n\n<p>```js\nconst doc = runner({ search }, {\n  evalFn: evalBody,\n  onExtructionError: (err, headingNode) => {\n    console.warn(\n      \\`Extruction \"\\${headingNode.data?.label}\" failed:\\`,\n      err.message,\n    )\n  },\n})</p>\n\n<p>for await (const section of doc) {\n  // Sections after the failing extruction still appear\n}\n```</p>\n\n<p>Without the callback, errors propagate to the consumer's `for await` loop.\nWith the callback, the failing extruction is silently dropped and iteration\ncontinues with the next heading. The heading node gives access to the\nposition (`headingNode.position`) for source-mapped diagnostics.</p>\n\n<h3>Usage — Adapter with `_mdt_label`</h3>\n\n<p>The `_mdt_label` binding lets one adapter serve multiple extruction variants:</p>\n\n\n<p>```js\nconst md = `# ${search mdd}</p>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.uri). join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h1>${search js}</h1>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => \"- \" + i.name). join(\"\\n\"))\n\\`\\`\\`\n`</p>\n\n<p>const search = async (q) => {\n  if (q === \"search mdd\") return [{ name: \"readme\", uri: \"#/readme\" }]\n  return [{ name: \"main.js\", uri: \"#/main.js\" }]\n}</p>\n\n<p>const runner = compile(md, { remark })\nconst doc = runner({ search }, { evalFn: evalBody })\n```</p>\n\n<p>The same `search` adapter is called with the label as its argument — no need\nto hardcode adapter names per extruction.</p>\n\n<h3>Usage — State across extructions</h3>\n\n<p>The runner automatically injects `mdtState` — a plain object that persists\nacross extruction evaluations within the same document:</p>\n\n<p>```js\nconst md = `# ${init}</p>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\nmdtState.items = [\"a\", \"b\", \"c\"]\n\\`\\`\\`</p>\n\n<h1>${first}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[0]}\\` )\n\\`\\`\\`</p>\n\n<h1>${second}</h1>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn insert( \\`Item \\${mdtState.counter}: \\${mdtState.items[1]}\\` )\n\\`\\`\\`\n`;</p>\n\n<p>const runner = compile(md, { remark });\nconst doc = runner({}, { evalFn: evalBody });</p>\n\n<p>for await (const section of doc) {\n  console.log(section.toString());\n  // \"${init}\" → transparent (no return/insert)\n  // \"${first}\" → \"Item 1: a\"\n  // \"${second}\" → \"Item 2: b\"\n}\n```</p>\n\n<p>`mdtState` is just a `{}` — the extruction body sets properties on it, and\nsubsequent evaluations read them back. It's automatically available in every\nextruction body without being added to the runner context.</p>\n\n<p>Callers can pre-populate `mdtState` by passing it in the context:</p>\n\n<p>```js\nconst doc = runner(\n  { mdtState: { repo: \"my-repo\", branch: \"main\" } },\n  { evalFn: evalBody },\n);\n```</p>\n\n\n<p>```</p><h2>${header}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Repo: \\${mdtState.repo}, branch: \\${mdtState.branch}\\` )\n\\`\\`\\`\n```</p>\n\n<p>This is useful when extructions need shared initialization or cross-section\ncommunication without resorting to global variables.</p>\n\n<p><b>Why this works:</b> `mdtState` is a single object stored on `runnerContext`.\nEach eval call spreads `runnerContext` into the function parameters, but the\nspread copies the reference — all evaluations share the same `mdtState` object.\nProperty mutations (set/add/delete) persist; reassigning `mdtState = ...` would\nonly affect the local parameter.</p>\n\n<h3>Phases</h3>\n\n<p>The runner materializes the document in phases:</p>\n\n<p>| Phase | What's yielded            | Work done                                              |\n| ----- | ------------------------- | ------------------------------------------------------ |\n| 1     | Root headings (level `#`) | Walk top-level children, skip extructions              |\n| 2+    | Children of a section     | Walk child headings, skip extructions, yield Fragments |</p>\n\n<p>No phase happens until the consumer pulls.</p>\n\n<h2>Fragment</h2>\n\n<p>A heading + its immediate body content.\nA fragment is the core unit the runner yields and the consumer navigates.</p>\n\n\n<p>```js\n{\n  trail: \"getting-started/installation\", // trail-id identifying this heading\n  heading: \"# Chapter 1\",       // raw markdown heading string\n  headingLevel: 1,              // number of # characters\n  body: \"Some introductory text.\", // canonicalized markdown body (no children)\n  hasChildren: true,            // does this fragment have expandable children?\n  expand(): AsyncIterable<Fragment>, // yields child fragments\n  toString(): \"# Chapter 1\\n\\nSome introductory text.\" // heading + body\n}\n```</p>\n\n<ul><li>`trail` — the trail-id that uniquely identifies this heading in\n  the document hierarchy.\n  Computed lazily using the stack algorithm when\n  the fragment is first materialized</li><li>`heading` — the heading as markdown source (e.g. `\"## Details\"`)</li><li>`headingLevel` — depth (1 for `#`, 2 for `##`, etc.)</li><li>`body` — the immediate body text, <b>canonicalized</b>\n  (parsed nodes rendered back to markdown).\n  Not byte-identical to source: remark normalizes list markers,\n  emphasis characters, wrapping.\n  If verbatim fidelity is required, use the source position (`node.position`)\n  to slice the original text. Does NOT include child fragments.</li><li>`hasChildren` — quick check without triggering expansion</li><li>`expand()` — returns an async iterable of child `Fragment` objects.\n  Each child is itself expandable and carries its own trail.</li><li>`toString()` — concatenates `heading + \"\\n\\n\" + body`, rendered as\n  markdown. Convenience for getting a fragment's full self-contained markdown.</li></ul>\n\n<p><b>AST source:</b> currently the fragment is materialized from remark's parsed\nAST. In the future it could come from the ast-nodes database\n(`cache_ast_lake_nodes` with `sem = 'heading'`), where each row carries\n`{ id, mt, sem, num1, num2, ref }` and `nomen` is derived from `ref`.\nThe fragment shape is designed to be mappable to/from that schema:\n`trail` ↔ `id`, `heading` ↔ `ref`, `headingLevel` ↔ `sem`.</p>\n\n<h3>expand() traversal</h3>\n\n<p>`expand()` walks the remark AST child heading nodes:</p>\n\n<ol><li>Walk child nodes left-to-right in document order.</li><li>When hitting a heading that\n   is <b>not</b> an extruction → yield a child `Fragment`.\n   Its body is the run of non-heading nodes up to\n   the next heading at the same level.</li><li>When hitting an <b>extruction</b> heading → skip (inert, no output).</li><li><b>Other nodes</b> (paragraphs, lists, etc.) → accumulate into the current\n   fragment's body.</li></ol>\n\n<p><b>Body boundary rule:</b> content before the first child heading belongs to\nthe parent's `body`; content between child heading <i>N</i> and\nthe next heading belongs to child <i>N</i>'s `body`.</p>\n\n<h3>Lazy guarantees</h3>\n\n<ul><li>`expand()` does nothing until iterated</li><li>Iterating past the first few fragments doesn't process later fragments</li></ul>\n\n<h2>Extruction</h2>\n\n\n<p>```</p><h2>${label}</h2>\n\n<p>\\`\\`\\`javascript\n// body code — only ```javascript blocks are evaluated\n\\`\\`\\`\n```</p>\n\n<p>An extruction is a `# ${...}` heading.\nWhen `evalFn` is provided, the body is evaluated as JavaScript —\nbut <b>only code inside ` ```javascript ` code blocks</b> is extracted.\nAny other markdown content in the body is ignored.\nWithout `evalFn`, the extruction and its body are silently dropped.</p>\n\n<p>| Property  | Value                                                                           |\n| --------- | ------------------------------------------------------------------------------- |\n| Detection | Heading text starts with `${`                                                   |\n| Body      | JavaScript code in ` ```javascript ` code blocks (only when evalFn is provided) |\n| Effect    | Removed from output; children promoted                                          |</p>\n\n<p>The `data.label` (text between `${}`) is available on the heading node for\nfuture processing but has no current effect.</p>\n\n<h3>Transparency semantics</h3>\n\n<p>Extructions are <b>fully transparent</b> — they produce no output and their\nbody content is silently dropped, but non-extruction headings nested under\nan extruction are <b>promoted</b> to the nearest non-extruction ancestor's\n`expand()` output. Their trail is computed as if the extruction doesn't exist.</p>\n\n<p>Implementation: `skipExtructionBody(startIdx, rootChildren)` advances past\nan extruction's non-heading content but stops at any heading (a promoted child),\nrather than skipping the entire subtree. This is used by `expandChildren`,\n`collectBodyNodes`, and `hasNonExtructionChild` to maintain consistency.</p>\n\n<h2>Error Handling</h2>\n\n<p><b>Compile-time</b> (thrown by `compile()`):</p>\n\n<ul><li>Unparseable markdown (remark parse failure)</li></ul>\n\n<p><b>Runtime</b> (caught by `onExtructionError` callback):</p>\n\n<ul><li>Syntax errors in extruction body JS</li><li>Runtime exceptions during extruction evaluation</li></ul>\n\n<p>When an extruction body throws during evaluation, the behavior depends on the presence\nof `onExtructionError`:</p>\n\n<p>| Callback                          | Behavior                                                                                                                                                       |\n| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <b>Provided</b>                      | Error is passed to `onExtructionError(err, headingNode)`; the extruction is treated as <b>transparent</b> (body skipped, children promoted). Iteration continues. |\n| <b>Not provided</b> (`null`/omitted) | Error <b>propagates</b> to the consumer's `for await` loop (backward compatible).                                                                                 |</p>\n\n<p>In `children` resolution, an errored child extruction follows the same rule — treated\nas transparent, its children promoted into the parent's `children` output.</p>\n\n<p>All errors include the source position (`node.position`) for debugging.</p>\n\n<h2>Open Questions</h2>\n\n<h3>1. What is `context` for?</h3>\n\n<p><b>Resolved:</b> `context` is <b>state</b> — a bag of global variables\nthat the document can reference.\nWith `evalFn`, extruction bodies can access context keys as named\nparameters. Without `evalFn`, `context` is accepted but unused.</p>\n\n<p>The runner signature stays `runner(context, opts?)`.\nWith no active extructions, `context` is accepted but unused — a\nforward-looking parameter.</p>\n\n<h3>2. Extruction label semantics</h3>\n\n<p><b>Deferred.</b> `data.label` is a free-form string — the text between `${}`.\nIts semantics are intentionally undefined until extruction evaluation\nis designed. Currently just stored, no effect.</p>\n\n<h3>3. When will extruction bodies activate?</h3>\n\n<p><b>Resolved.</b> Extruction bodies are evaluated as JavaScript when `evalFn` is\nprovided. Only ` ```javascript ` code blocks within the body are extracted —\nnon-javascript code blocks and other markdown content are ignored.\nWithout `evalFn`, the body remains inert (silently dropped).</p>\n\n<h3>4. Verbatim vs canonicalized body</h3>\n\n<p><b>Resolved.</b> `body` is canonicalized by default (re-stringified remark\nnodes). Source position (`node.position`) is the escape hatch for\nverbatim access. No default flip — canonicalized is the correct default\nbecause consumers should get consistent, predictable markdown output.\nIf verbatim is needed, slice the original text using source offsets.</p>\n\n<h3>5. `hasChildren` and extructions</h3>\n\n<p><b>Resolved — extructions are fully transparent with child promotion.</b>\nExtructions are skipped from both output and navigation. Non-extruction\nheadings nested under an extruction are <b>promoted</b> to the parent's\n`expand()` output:</p>\n\n<ul><li>`hasChildren` reports what `expand()` would yield — this includes\n  promoted children under extructions.</li><li>Child headings nested under an extruction get their trail computed\n  as if the extruction doesn't exist — they attach to the nearest\n  non-extruction ancestor heading.</li><li>Extruction body content is still silently dropped; only the promoted\n  heading (and its own subtree) survives.</li><li>`skipExtructionBody()` is the shared helper that implements this:\n  given an extruction heading index, it advances past non-heading body\n  content but returns at the first heading (promoted child) rather than\n  skipping the entire subtree.</li><li>Consistency invariant: `expand()`, `hasChildren`, `collectBodyNodes`,\n  and `findInHeadings` all agree on which headings are reachable.</li><li>Rationale: extructions are inert markers by default; their body is\n  dropped (or evaluated with `evalFn`), but document structure under\n  them is preserved.</li></ul>\n\n<h2>App Integration</h2>\n\n<p>The MDT library is integrated into `player-paper.js` at the `\"mdt\"` case\nof the extension switch (line 876). When a `.mdt` file is opened:</p>\n\n<ol><li><b>Dynamic imports</b>: `remark` + `remark-parse` loaded from CDN\n   (`cdn.jsdelivr.net`); `compile` imported from `./mdt/mdt.js`</li><li><b>Fetch</b>: file content fetched via `ssss.fetchWithETag()` with ETag caching</li><li><b>Compile</b>: `compile(data, { remark })` → `Runner`</li><li><b>Run</b>: `runner(STATE)` → `Document` (STATE serves as context)</li><li><b>Rebuild clean markdown</b>: fragments recursively collected via\n   `collectFragments()` async generator, each fragment's `toString()`\n   produces heading + body with extructions already filtered</li><li><b>Render</b>: clean markdown rendered via `ssss.renderMarkdown()`</li><li><b>Post-process</b>: heading tabindex, relative image URL resolution</li></ol>\n\n<p>The current integration uses the browser's dynamic `import()` for remark\n(same CDN source as `mdd.mjs`). The `context` parameter passes the app's\nSTATE object, with adapters mixed in for extruction evaluation.</p>\n\n<h2>Extruction Evaluation</h2>\n\n<p>Extruction bodies can be evaluated as JavaScript at runtime when the `evalFn`\noption is passed to the runner. This enables `# ${...}` headings to produce\ndynamic content.</p>\n\n<h3>evalBody</h3>\n\n<p>`mdt/eval-body.js` exports the default evaluation function:</p>\n\n\n<p>```\nevalBody(bodyText, context) → Promise<any>\n```</p>\n\n<p>It uses the `AsyncFunction` constructor (same pattern as\n`evalJsFilterWithContext` in `filter-base.js`) to evaluate the body text as\nJS code with the context keys available as named parameters.</p>\n\n<p>```js\nimport { evalBody } from \"./mdt/eval-body.js\";</p>\n\n<p>const doc = runner({ search, STATE }, { evalFn: evalBody });\n```</p>\n\n<p>Inside an extruction body, any key from the context is directly accessible:</p>\n\n\n<p>```</p><h2>${the list}</h2>\n\n<p>\\`\\`\\`javascript\nconst x = await search(\"mdd\")\nreturn insert( x.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<h3>Extruction return value — `insert()` / `inject()` built-ins</h3>\n\n<p>When `evalFn` is provided, the extruction body has access to auto-injected\nhelpers and data (like `_mdt_label`, `mdtState`, and `log`):</p>\n\n<ul><li><b>`insert(children)`</b> — pipe Fragment-like objects directly into the output</li><li><b>`inject(text)`</b> — produce a single raw-body Fragment with no heading</li><li><b>`children`</b> — markdown text of the extruction's child subtree (headings between this extruction and the next heading at same/higher depth)</li></ul>\n\n<h4>`insert(children)`</h4>\n\n<p>Takes one or more Fragment-like objects and yields each as-is into the output\nstream. No wrapping, no heading comment — the caller has full control:</p>\n\n\n<p>```</p><h2>${search results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert(items.map(r => ({\n  trail: _mdt_label + \"/\" + r.id,\n  heading: \"### \" + r.name,\n  headingLevel: 3,\n  body: r.description,\n  hasChildren: false,\n  expand: () => (async function* {})(),\n  toString: () => \"### \" + r.name + \"\\n\\n\" + r.description,\n})))\n\\`\\`\\`\n```</p>\n\n<p>Pass a single fragment or an array — `insert()` handles both:</p>\n\n<p>```js\nreturn insert(singleFrag);\nreturn insert([fragA, fragB, fragC]);\n```</p>\n\n<h4>`inject(text)`</h4>\n\n<p>Takes a string and yields a single raw-body Fragment with no heading, no trail,\nno wrapper:</p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>The Fragment has `heading: \"\"`, `headingLevel: 0`, `trail: \"\"`, and\n`toString()` returns the raw body.</p>\n\n<h4>`children` — recursively resolved child subtree</h4>\n\n<p>The `children` variable holds the resolved output of the extruction's child\nsubtree — all headings between this extruction and the next heading at the\nsame or higher depth. Non-heading body text after the extruction heading is\n<b>not</b> included (that's the `bodyText` passed to `evalFn`).</p>\n\n<p>Resolution is <b>recursive</b> — `children` is computed by walking the child\ntree and processing each node:</p>\n\n<p>| Child type                                           | Treatment                                                                                                             |\n| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| <b>Extruction</b> (with result)                         | Evaluated with its own recursive `children`; its output (`inject`/`insert` bodies) is stringified and included        |\n| <b>Extruction</b> (transparent — `undefined`/no return) | Body skipped; children promoted and recursively resolved                                                              |\n| <b>Extruction</b> (suppressed — `null`)                 | Entire subtree dropped — children do not appear in parent's `children`                                                |\n| <b>Extruction</b> (errored, with `onExtructionError`)   | Caught; treated as transparent — children promoted (same as `skipExtructionBody`)                                     |\n| <b>Regular heading</b>                                  | Heading text + body text preserved as markdown; its own child subtree recursively resolved for any nested extructions |</p>\n\n<p>This means extructions at any depth are fully evaluated — a `##### ${...}`\ndeep under a regular `####` heading will still produce its resolved output.</p>\n\n<p>A common pattern is to pipe children through `insert()`:</p>\n\n\n<p>```</p><h2>${list of todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [inject(\"> Generated list:\\n\\n\"), insert(children)]\n\\`\\`\\`\n```</p>\n\n<p>`children` is an empty string `\"\"` when:</p>\n\n<ul><li>The extruction has no child headings</li><li>The extruction is at root level with no children</li></ul>\n\n<p>Non-extruction headings are included as original markdown (source positions\npreserve formatting). Extruction headings themselves never appear in the\noutput — they're transparent, only their resolved content is included.</p>\n\n<h4>`insertRefsAsSubtree(items, opts?)`</h4>\n\n<p>Turn an array of fragment refs (typically `await search(...)` results) into\nchild-depth heading Fragments with <b>lazy body-fetch</b>:</p>\n\n\n<p>```</p><h2>${search fragments; do}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsSubtree(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n<p>Each item becomes ONE Fragment one level <b>below</b> the extruction\n(`extruction.depth + 1`), so the results nest as children of the current\nlevel. The Fragment's body is empty at yield-time; the fetch happens only\ninside its `expand()` — i.e. only when the render pipeline walks into that\nsubtree. Depth is clamped at 6 (markdown's maximum heading level).</p>\n\n\n<p>```</p><h2>insertRefsAsSubtree      ← depth 2, visible parent</h2><h3>${insertRefsAsSubtree}  ← depth 3, extruction (filtered from output)</h3><h4>auth                   ← depth 4, one Fragment per item</h4><h5>…transcluded body…    ← depth 5+, from loadRefBody</h5><p>```</p>\n\n<p>This is the only verb whose heading is real markdown — every other verb\nemits an HTML-comment heading, so its depth is invisible.</p>\n\n<p><b>Item contract (minimum):</b></p>\n\n<p>| Field                              | Purpose                                                                                                                                                                                                                                                    |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `nomen` / `ref` / `trail` / `name` | Heading text — resolves in order: `nomen` (pre-computed) → `ref.split(\";\").at(-1)` (leaf of the semicolon-trail, matching `cmdDashboard.js` / `cmdTreeview.js` convention) → `trail.at(-1)` (parsed-array form) → `name` (URL-style, last-resort fallback) |\n| `fn`                               | Source file path                                                                                                                                                                                                                                           |\n| `trail` (array)                    | Preferred — used to build canonical refId                                                                                                                                                                                                                  |\n| `num1` (number)                    | Fallback when trail is absent                                                                                                                                                                                                                              |</p>\n\n<p>Items missing `name`/`ref`, or without both `fn` and (`trail` or `num1`),\nare skipped with `console.warn`. **If every item is skipped, a visible\nblockquote is emitted** explaining why — the verb never fails silently.</p>\n\n<p>The common cause is feeding it the wrong search source: `files` results\n(`{name, uri, fn, type:\"file\"}`) carry no `trail`/`num1`, so there is no\nsubtree to resolve. Use a `fragments` query, whose items carry\n`nomen`/`trail`/`num1`/`fn`.</p>\n\n<p><b>opts:</b></p>\n\n<p>| Field   | Purpose                                                      |\n| ------- | ------------------------------------------------------------ |\n| `depth` | Absolute override of the auto depth (`extruction.depth + 1`) |</p>\n\n<p><b>Runner opt required:</b> `runner(ctx, { evalFn, loadRefBody })`. If\n`loadRefBody` is not provided, each Fragment renders heading-only.</p>\n\n<h4>`insertNljson(collection, opts?)`</h4>\n\n<p>Serialize a collection as newline-delimited JSON inside an ` ```nljson `\nfence — one JSON object per line:</p>\n\n\n<p>```</p><h2>${rows}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertNljson([{ a: 1 }, { b: 2 }])]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"a\":1}\n{\"b\":2}\n```</p>\n\n<p>A single non-array value is wrapped. This is a <b>raw passthrough</b> — values\nare serialized as given, so nested objects and arrays survive. That makes it\nunsuitable for feeding a table directly: `insertNljson(await search(...))`\nemits `trail` arrays, and Tabulator's `html` formatter throws\n`Formatter has returned a type of object`. Use `insertRefsAsNljson` for\ntable-bound ref data, or pick scalar fields yourself.</p>\n\n<h4>`insertRefsAsList(items, opts?)`</h4>\n\n<p>Render an array of refs as a markdown bullet list — a flat alternative to\n`insertRefsAsSubtree` with no lazy fetch:</p>\n\n\n<p>```</p><h2>${links}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsList(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```</p><ul><li><a href=\"#/paper/todo.mdd/auth\">auth</a> {{\"platba\":{\"suma\":42}}}</li><li><a href=\"#/paper/a.mdd\">login</a></li><li>plain\n```</li></ul>\n\n<p>Labels resolve with the same 4-step rule as `insertRefsAsSubtree`. An item\nwith `uri` becomes a markdown link; without one it stays plain text. Items\nwith no resolvable label are skipped with `console.warn`.</p>\n\n<p>| opts     | Purpose                                  |\n| -------- | ---------------------------------------- |\n| `bullet` | List marker, default `\"-\"`               |\n| `data`   | `false` suppresses the `{…}` data suffix |\n| `source` | Conversion-tree provenance tag           |</p>\n\n<h4>`insertRefsAsNljson(items, optsOrFn?)`</h4>\n\n<p>Render an array of refs as nljson rows — reuses `insertNljson`'s fence, but\nbuilds each row from the ref and guarantees <b>table-safe scalar cells</b>:</p>\n\n\n<p>```</p><h2>${table}</h2>\n\n<p>\\`\\`\\`javascript\nreturn [insertRefsAsNljson(await search(_mdt_label))]\n\\`\\`\\`\n```</p>\n\n\n<p>```nljson\n{\"link\":\"<a href=\\\"#/paper/todo.mdd/auth\\\">auth</a>\",\"data\":\"{\\\"platba\\\":{\\\"suma\\\":42}}\"}\n```</p>\n\n<p>`link` is an <b>HTML anchor</b> (not a markdown link) because nljson usually\nfeeds a table — the table needs `columnDefaults: { formatter: 'html' }` to\nrender it. The `uri` is attribute-escaped (`&` → `&amp;`, `\"` → `&quot;`).</p>\n\n<p>Every row value is flattened before output: any object or array becomes a\nJSON string. This is what keeps Tabulator's `html` formatter from throwing\non `trail` arrays or nested `data`.</p>\n\n<p><b>Second argument — object or function.</b> A bare function is shorthand for\n`{ extend: fn }`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn [\n  insertRefsAsNljson(mdtState.items, function addAdditionalProperties(i) {\n    const data = i.data ? JSON.parse(i.data) : undefined\n    return {\n      suma: data?.platba?.suma,\n      data: JSON.stringify(data),\n    }\n  }),\n]\n\\`\\`\\`\n```</p>\n\n<p>`extend(item, row)` receives the <b>raw</b> item first (so `item.data` is the\nuntouched string) plus the base row, and its returned props are merged over\nthe auto-built ones — the example above replaces the auto `data`. Keys whose\nvalue is `undefined` are dropped from the row rather than emitted as `null`,\nso ragged rows are normal.</p>\n\n<p>| opts     | Purpose                                                                                                     |\n| -------- | ----------------------------------------------------------------------------------------------------------- |\n| `extend` | `(item, row) => ({…})` — per-item extra props, merged last. A bare function argument is shorthand for this  |\n| `fields` | Array of item field names to copy through, e.g. `['scaledTs']`                                              |\n| `data`   | `false` drops the auto `data` column                                                                        |\n| `map`    | `(row, item) => row` — replaces the whole row; runs after `extend` and sees parsed values before flattening |\n| `source` | Conversion-tree provenance tag                                                                              |</p>\n\n<h4>`buildUrl(content, mimeType?)`</h4>\n\n<p>Not a command — a plain helper returning a base64 data URI via `btoa()`.\nDefaults to `text/plain`:</p>\n\n\n<p>```\n\\`\\`\\`javascript\nreturn <a href=\"${buildUrl(JSON.stringify(rows\">inject(`[download</a>, \"application/json\")})`)]\n\\`\\`\\`\n```</p>\n\n<h4>Mixed output</h4>\n\n<p>Return an array of calls to produce multiple items in sequence:</p>\n\n\n<p>```</p><h2>${mixed}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nconst cards = items.map(r => ({ /<i> fragment shape </i>/ }))\nreturn [inject(\"> Preview below:\\n\\n\"), insert(cards)]\n\\`\\`\\`\n```</p>\n\n<p>Each item in the array is a command object produced by any of the verbs —\n`insert()`, `inject()`, `insertNljson()`, `insertRefsAsList()`,\n`insertRefsAsNljson()`, or `insertRefsAsSubtree()` — mixable in any order.</p>\n\n<h4>Return nothing</h4>\n\n<ul><li><b>Omit `return` or return `undefined`</b> — the extruction stays transparent\n  (no output, children promoted as if the extruction didn't exist).</li><li><b>Return `null`</b> — the extruction is removed and its children are\n  <b>suppressed</b> (dropped entirely, not promoted).</li></ul>\n\n<h4>State still via `mdtState`</h4>\n\n<p>The `mdtState` object is mutated directly through property assignment, not\nthrough helpers:</p>\n\n\n<p>```</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter = 0\n\\`\\`\\`</p>\n\n<h2>${count}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.counter++\nreturn inject(String(mdtState.counter))\n\\`\\`\\`\n```</p>\n\n<h4>Adapters — `search`, `searchVotes`, `votesAsRefs`</h4>\n\n<p>Adapters are <b>not</b> commands. They are async functions injected into the\neval context by `createAdapters()` (`adapters.js`) and used to <i>obtain</i>\nitems, which the `insert*` verbs then render. All three are `await`-ed.</p>\n\n<p>| Adapter              | Input                 | Returns                             |\n| -------------------- | --------------------- | ----------------------------------- |\n| `search(query)`      | glass-search string   | ref items (`fragments`, `files`, …) |\n| `searchVotes(query)` | `{ campaign, repo? }` | vote rows from `v_voting_campaign`  |\n| `votesAsRefs(votes)` | vote rows             | ref items                           |</p>\n\n<p>`searchVotes` queries the `v_voting_campaign` view. `repo` defaults to\n`STATE.repoName`. `campaign` accepts `'*'` (all campaigns), a prefix, or an\narray of prefixes — matching is by <b>prefix, not exact name</b>:</p>\n\n<p>| `campaign`   | SQL condition                                    |\n| ------------ | ------------------------------------------------ |\n| `'*'`        | `1` — no filter                                  |\n| `'do'`       | `campaign GLOB 'do:*'`                           |\n| `['a', 'b']` | `( campaign GLOB 'a:<i>' OR campaign GLOB 'b:</i>' )` |\n| `[]`         | none — returns `[]` without querying             |</p>\n\n<p>This mirrors `campaignPrefix` in `tagCloudByVotingsFromView()`. A consequence\nworth remembering: an exact campaign name matches only if something sits\nbelow it, so pass the parent prefix rather than the full campaign.</p>\n\n<p>Rows come back as objects:</p>\n\n\n<p>```\nrepo campaign nomen aliasRef id num1 voteCount maxCount rn\n```</p>\n\n<p>`score` is <b>not</b> selected — the deployed view may have been generated with\n`withScore: false`, and its `LN()` also needs a SQLite built with\n`SQLITE_ENABLE_MATH_FUNCTIONS`. It is computed locally instead, from\n`voteCount / maxCount`, and added to each row:</p>\n\n<p>```js\n1 + Math.round(Math.log1p((voteCount / maxCount) * 100));\n```</p>\n\n<p>Verified identical to the view's SQL expression across the real vote rows.</p>\n\n<p>`votesAsRefs` is a pure conversion — vote rows carry `aliasRef`, `id` and\n`num1`, which is everything a ref item needs. It builds `uri` the same way a\n`fragments` search does (`#/paper/${aliasRef}`, falling back to\n`legacyPaperUrl`), sets `nomen` for the label, and derives `fn` by stripping\nthe `:NNNN` node-seq suffix off `id` so `buildRefId()` resolves. Vote data\n(`campaign`, `voteCount`, `maxCount`, `score`, `rn`) rides along, so\n`insertRefsAsNljson` can surface counts without a second query.</p>\n\n<p>It is `async` despite doing no I/O today — the signature is the contract, so\na later version can enrich from the DB without breaking callers.</p>\n\n<p><b>Example — list voted fragments:</b></p>\n\n\n<p>```md</p><h2>${init}</h2>\n\n<p>\\`\\`\\`javascript\nmdtState.queryVotes = { campaign: '*' }\nmdtState.votes = await searchVotes(mdtState.queryVotes)\n\\`\\`\\`</p>\n\n<h3>${list}</h3>\n\n<p>\\`\\`\\`javascript\nreturn [\n  insertRefsAsList(await votesAsRefs(mdtState.votes)),\n]\n\\`\\`\\`\n```</p>\n\n<p>Both are wired in `adapters.js` exactly as `search` is, so anything that\nbuilds a runner context gets them for free.</p>\n\n<h4>Command contract — all verbs</h4>\n\n<p>| Helper                                 | Input      | Fragments            | Body                                            |\n| -------------------------------------- | ---------- | -------------------- | ----------------------------------------------- |\n| `insert(x, opts?)`                     | anything   | 1                    | array→`\\n`-joined, object→JSON, else `String()` |\n| `inject(s)`                            | `string`   | 1                    | raw passthrough, no heading, empty trail        |\n| `insertNljson(x, opts?)`               | collection | 1                    | ` ```nljson ` fence, one JSON per line          |\n| `insertRefsAsList(items, opts?)`       | ref items  | 1                    | `- <a href=\"uri\">nomen</a> {data}` bullet list             |\n| `insertRefsAsNljson(items, optsOrFn?)` | ref items  | 1                    | ` ```nljson ` fence, scalar cells, auto `link`  |\n| `insertRefsAsSubtree(items, opts?)`    | ref items  | <b>N</b> (one per item) | heading-only; body fetched lazily in `expand()` |</p>\n\n<p>`buildUrl(content, mimeType?)` is a helper, not a command — it returns a\n`data:` URI string for use inside any of the above.</p>\n\n<p><b>`insertRefsAsSubtree` is the structural odd one out.</b> Every other verb\nyields exactly one leaf Fragment (`hasChildren: false`, inert `expand()`)\nwhose heading is an invisible HTML comment. `insertRefsAsSubtree` fans out\nto one Fragment <i>per item</i>, each with a real visible heading, `hasChildren:\ntrue`, and a real `expand()` that calls `loadRefBody` — so the content fetch\nis deferred until the render pipeline walks into that subtree. It also\ndedupes colliding trails with `-2`/`-3` suffixes.</p>\n\n<p><b>`source` tagging</b> (conversion-tree provenance) rides on `insert`,\n`insertNljson`, `insertRefsAsList`, and `insertRefsAsNljson`. `inject` never\ncarries it; `insertRefsAsSubtree` derives `sourceFragment` itself from\n`buildRefId(item)`.</p>\n\n<p><b>Two dispatch sites</b> handle these: `processExtructionResult` yields real\nFragments, while the array walker in `resolveChildTree` stringifies commands\ninto a parent's `children` text. `insertRefsAsSubtree` is deliberately absent\nfrom the second — nested inside a `children` resolution there is no lazy\nexpansion in a flat string context, so it contributes nothing there.</p>\n\n<p>Under the hood every helper produces a command object\n(`{ insert: [...] }` / `{ inject: \"...\" }` / …) that the runner processes.\nThe extruction must return an array `[cmd1, cmd2, ...]` to yield fragments.\nA bare non-array object yields nothing — only `undefined` or an array is valid.</p>\n\n<p><b>Example — injecting a preamble:</b></p>\n\n\n<p>```</p><h2>${notice}</h2>\n\n<p>\\`\\`\\`javascript\nreturn inject(\"> <b>Note:</b> this document is generated from live data.\")\n\\`\\`\\`\n```</p>\n\n<p>This produces a Fragment whose `toString()` is just the blockquote — no\nheading comment wrapping it. The consumer sees clean markdown without\nsynthetic HTML comments.</p>\n\n<p><b>Implementation notes:</b></p>\n\n<ul><li>`buildInjectFragment(injectValue)` in `mdt.js` creates the Fragment with\n  `body = normalizeFragmentBody(injectValue)` — same serialization as\n  `buildInsertFragment` (array→joined, object→JSON, primitive→String).</li><li>`normalizeFragmentBody()` is the shared helper used by both protocols,\n  extracted during the inject implementation.</li><li>`processExtructionResult()` (the async generator in `mdt.js`) iterates\n  each command in the array and yields a Fragment per command — `insert`\n  and `inject` can be mixed in any order.</li><li>Non-array results are silently ignored (yield nothing). Only `undefined`\n  (skip) or `[cmd, ...]` (yield) are valid return values.</li><li>`inject` fragments have `hasChildren: false` and `expand()` returns an\n  empty async generator — they are always leaf nodes.</li></ul>\n\n<h3>hasChildren & extruction evaluation</h3>\n\n<p>When `evalFn` is active, any extruction child heading causes the parent's\n`hasChildren` to be `true`, since the extruction might produce an `insert`.\nThis ensures `rebuildMd()`-style collectors expand to find evaluated content.\nExtructions that evaluate to `undefined` yield no children (the expansion\nreturns empty immediately).</p>\n\n<h3>Error behavior</h3>\n\n<ul><li><b>No evalFn</b> — extruction bodies are inert (silently dropped).</li><li><b>evalFn provided, body has JS syntax error</b> — `SyntaxError` propagates.</li><li><b>evalFn provided, runtime error</b> — error propagates from the evaluation.</li></ul>\n\n<p>The snapshot test `\"syntax error in extruction body\"` documents the current\nbehavior without `evalFn` (silently dropped). When `evalFn` is added to that\ntest, it should throw.</p>\n\n<h3>buildInsertFragment serialization</h3>\n\n<p>`buildInsertFragment(insertValue, ...)` handles the `{ insert }` value:</p>\n\n<ul><li><b>Array</b> — mapped item-by-item (objects `JSON.stringify`, primitives `String`),\n  joined with `\"\\n\"`</li><li><b>Object (non-array)</b> — `JSON.stringify`</li><li><b>Primitive</b> — `String()`</li></ul>\n\n<p>This prevents `[object Object]` output when extruction bodies return arrays or\nobjects (e.g. search results).</p>\n\n<h3>Probes</h3>\n\n<p>Two `console.log` probes are placed at the extruction result handling points:</p>\n\n<ul><li>`probe:mdt-ext-result` — in `expandChildren()`, fires after evalFn returns\n  for a non-root extruction. Logs `{ heading, result, hasInsert }`.</li><li>`probe:mdt-ext-root-result` — in the root iterator, same shape for root-level\n  extructions.</li></ul>\n\n<p>These are the frontend equivalent of the backend probe pattern\n(`PROXY.remoteState?.log({ label })`). The MDT library is a pure frontend\nmodule without PROXY access, so `console.log` is used directly.</p>\n\n<h2>Search Adapter</h2>\n\n<p>The MDT library provides a search adapter that wraps the app's `glassSearchRun()`\nwith proper async completion detection, emitting per-source events and a\nfinal `allCompletedDone` event.</p>\n\n<h3>glassSearchRunAsync</h3>\n\n<p>`mdt/glass-search-run.js` exports an async wrapper around the app's\n`glassSearchRun()`:</p>\n\n\n<p>```\nglassSearchRunAsync(queryString, ssss, state, STATE, route, prevHashRoute, proxy)\n  → { onSource(fn), onComplete(fn), then(resolve, reject) }\n```</p>\n\n<p>The wrapper:</p>\n\n<ol><li>Passes a mock `menuInput` to `glassSearchRun` (the autocomplete instance is\n   irrelevant for programmatic use)</li><li>Wraps `proxy.addResultItems` to emit `source` events — each call to\n   `addResultItems` fires `onSource(items)` with the incoming results</li><li>Detects completion via a 50ms batch timer after the last `addResultItems` call,\n   then fires `onComplete(allResults)`</li><li>Handles sync-only sources (files/map) by resolving on the next microtick via\n   `setTimeout(0)`</li><li>Has a 5-second safety fallback for async sources</li></ol>\n\n<p>Returns a <b>thenable</b> object — supports both event-based and Promise-based usage:</p>\n\n<p>```js\n// Event-based\nconst search = glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\nsearch.onSource((items) => console.log(\"received\", items.length, \"results\"));\nsearch.onComplete((allResults) => console.log(\"all done\", allResults.length));</p>\n\n<p>// Promise-based\nconst allResults = await glassSearchRunAsync(\n  query,\n  ssss,\n  state,\n  STATE,\n  route,\n  prevHashRoute,\n  proxy,\n);\n```</p>\n\n<h3>search() adapter</h3>\n\n<p>`mdt/search-adapter.js` exports a thin convenience function:</p>\n\n\n<p>```\nsearch(query, ssss, state, STATE, route, prevHashRoute, proxy) → thenable\n```</p>\n\n<p>Returns empty results for empty/whitespace queries. Otherwise delegates to\n`glassSearchRunAsync`.</p>\n\n<h3>Completion detection</h3>\n\n<p>The \"tiny issue\" with `glassSearchRun()` is that it returns synchronously but\nkicks off async SQLite fragment searches (debounced at 5ms). The result list\n(`resultList` from `glass-search.js`) is populated incrementally:</p>\n\n<ol><li><b>Sync sources</b> (files, map) push directly to `resultList` inside `searchInRepoJson`</li><li><b>Debounced SQLite sources</b> (fragments, nodes, maps, content, links) arrive later:\n   `searchInFragments` → `proxy.addResultItems` → `resultList` is updated +\n   `menuInput.rerender()` is called</li><li><b>History source</b> arrives via `searchInHistory` → `proxy.addResultItems`</li></ol>\n\n<p>The wrapper intercepts `proxy.addResultItems` to know when async results arrive.\nA 50ms batch window absorbs cascaded calls, then `onComplete` fires with the\nfull, deduplicated result list.</p>\n\n<h2>Adapter Pattern</h2>\n\n<p>Adapters are <b>functions injected into the runner context</b> that extruction\nbodies can call as if they were local variables. The mechanism is simple:</p>\n\n<ol><li>The runner receives `context = { search, fetchDb, ... }` — keys are names,\n   values are functions or data</li><li>`evalBody()` uses `new AsyncFunction(...Object.keys(context), bodyText)`\n   — each context key becomes a named parameter of the compiled function</li><li>The extruction body can `await adapterName(...)` just like any JS function</li></ol>\n\n\n<p>```\nrunner(context, { evalFn: evalBody })\n//            ^— keys here become parameter names in extruction bodies\n```</p>\n\n<h3>How it works</h3>\n\n<p>Given this setup:</p>\n\n<p>```js\nconst doc = runner(\n  { search: mySearchFn, getUser: myGetUserFn },\n  { evalFn: evalBody },\n);\n```</p>\n\n<p>An extruction body like:</p>\n\n\n<p>```</p><h2>${find stuff}</h2>\n\n<p>\\`\\`\\`javascript\nconst results = await search(\"mdd\")\nreturn insert( results.map(r => r.name).join(\"\\n\"))\n\\`\\`\\`\n```</p>\n\n<p>...is compiled to something like `AsyncFunction(search, getUser, bodyText)`,\nso `search` and `getUser` are directly accessible in the body without any import.</p>\n\n<h3>Naming rules</h3>\n\n<ul><li>Keys must be <b>valid JS identifiers</b> (no hyphens, no leading digits)</li><li>Use <b>camelCase</b> — this is idiomatic for JS function names</li><li>Avoid the `<i>mdt</i>` prefix — that's reserved for library-injected names\n  (currently only `_mdt_label`)</li><li>Names that collide with JavaScript reserved words (`class`, `return`, `await`)\n  will break — if you need one, alias it: `{ searchClass: ..., ... }`</li></ul>\n\n<h3>Return protocol</h3>\n\n<p>Adapters can return anything — there's no adapter-specific protocol.\nThe extruction body is responsible for handling the return value and deciding\nwhat to do with it via the `insert` protocol:</p>\n\n<ul><li>`return insert(value)` — the extruction produces output</li><li>`return undefined` or no return — extruction stays transparent</li><li>`throw error` — propagates to the consumer (or caught by `onExtructionError`)</li></ul>\n\n<p>This means adapters can return raw data (arrays, objects, strings) and the\nextruction body formats it into markdown.</p>\n\n<h3>Adapter conventions</h3>\n\n<ol><li><b>Async by convention</b> — make adapters `async` even if they're sync.\n   The extruction body uses `await` consistently, and an `async` adapter that\n   happens to resolve synchronously is cheaper than a sync adapter that the\n   body wraps in `Promise.resolve()`.</li></ol>\n\n<ol><li><b>Error handling</b> — let errors propagate. The extruction body handles them\n   if needed, or `onExtructionError` catches globally.\n   Don't silently swallow errors in the adapter.</li></ol>\n\n<ol><li><b>`_mdt_label`</b> — each extruction has its label available as `_mdt_label`.\n   Adapters can receive it explicitly from the body:</li></ol>\n\n\n<p>```</p><h2>${search mdd}</h2>\n\n<p>   \\`\\`\\`javascript\n   return insert( await search(_mdt_label))\n   \\`\\`\\`\n   ```</p>\n\n<p>This is how the same adapter can be driven by different extruction labels\nwithout hardcoding the query string.</p>\n\n<h2>Example adapters</h2>\n\n<h3>1. Simple lookup (sync)</h3>\n\n<p>```js</p>\n\n<p>const repoInfo = {\nssss: { stars: 42, description: \"The ssss project\" },\nmdt: { stars: 12, description: \"Markdown construction pseudo-code\" },\n};</p>\n\n<p>const doc = runner({ repoInfo }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${repo info}</h2>\n\n<p>\\`\\`\\`javascript\nconst r = repoInfo[\"ssss\"]\nreturn insert( \\`<b>${r.stars}</b> stars — ${r.description}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>2. Search adapter</h3>\n\n<p>Already documented in <a href=\"#search-adapter\">Search Adapter</a>. The pattern:</p>\n\n<p>```js</p>\n\n<p>import { search } from \"./mdt/search-adapter.js\";</p>\n\n<p>const doc = runner(\n{ search: (q) => search(q, ssss, state, STATE, route, prevHashRoute, proxy) },\n{ evalFn: evalBody },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${results}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(\"mdd\")\nreturn insert( items.map(i => \\`- <a href=\"${i.uri}\">\\${i.name}</a>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The key insight: the adapter wraps the app's async search with completion\ndetection, but the extruction body just sees a function it can `await`.</p>\n\n<h3>3. HTTP fetch</h3>\n\n<p>```js</p>\n\n<p>const fetchJson = async (url) => {\nconst res = await fetch(url);\nif (!res.ok) throw new Error(`fetch ${url}: ${res.status}`);\nreturn res.json();\n};</p>\n\n<p>const doc = runner(\n{ fetchJson },\n{ evalFn: evalBody, onExtructionError: handleError },\n);</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${github stats}</h2>\n\n<p>\\`\\`\\`javascript\nconst data = await fetchJson(\"https://api.github.com/repos/user/repo\")\nreturn insert( \\`\\${data.stargazers_count} stars, \\${data.forks_count} forks\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The adapter is a thin wrapper around `fetch()` with error handling.\nThe extruction body destructures the response and formats it as markdown.</p>\n\n<h3>4. Database query</h3>\n\n<p>```js</p>\n\n<p>const queryDb = async (sql) => {\nconst db = await getDatabase();\nreturn db.exec(sql);\n};</p>\n\n<p>const doc = runner({ queryDb }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${active users}</h2>\n\n<p>\\`\\`\\`javascript\nconst rows = await queryDb(\"SELECT name, email FROM users WHERE active = 1\")\nreturn insert( rows.map(r => \\`- \\${r.name} <\\${r.email}>\\`).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<h3>5. State access</h3>\n\n<p>When the runner context includes the app's state object, extructions can read\nfrom it directly:</p>\n\n<p>```js</p>\n\n<p>const doc = runner({ STATE, currentUser: \"bebo\" }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${welcome}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( \\`Hello <b>\\${currentUser}</b>, you have \\${STATE.notifications.length} notifications\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>This is how the app passes its reactive state into extruction bodies.</p>\n\n<h3>6. Composition — multiple adapters</h3>\n\n<p>Adapters compose naturally since they're just JS functions:</p>\n\n<p>```js</p>\n\n<p>const doc = runner({ repoInfo, fetchJson, currentUser }, { evalFn: evalBody });</p>\n\n\n<p>```</p>\n\n<p>```</p>\n\n<h2>${dashboard}</h2>\n\n<p>\\`\\`\\`javascript\nconst user = currentUser\nconst repos = await fetchJson(\\`https://api.github.com/users/\\${user}/repos\\`)\nconst summary = repos.map(r => \\`- \\${r.name}: \\${repoInfo[r.name]?.description || \"unknown\"}\\`).join(\"\\n\")\nreturn insert( \\`### \\${user}'s repos\\n\\n\\${summary}\\` )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>Here `repoInfo` is a sync lookup, `fetchJson` is async, and `currentUser` is\na plain string — all coexist as named parameters.</p>\n\n<h3>7. Using `_mdt_label` to drive adapters</h3>\n\n<p>The label (text between `${}`) is injected as `_mdt_label` automatically.\nThis lets a single adapter serve multiple extruction variants:</p>\n\n<p>```</p>\n\n<h2>${fetch todos}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/todos\"))\n\\`\\`\\`</p>\n\n<h2>${fetch users}</h2>\n\n<p>\\`\\`\\`javascript\nreturn insert( await fetchJson(\"/api/users\") )\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>Without hardcoding the path in each body — although in this case you'd still\nneed to map the label to the path. A more practical use:</p>\n\n<p>```</p>\n\n<h2>${search mdd}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.uri).join(\"\\n\"))\n\\`\\`\\`</p>\n\n<h2>${search js}</h2>\n\n<p>\\`\\`\\`javascript\nconst items = await search(_mdt_label)\nreturn insert( items.map(i => i.name).join(\"\\n\"))\n\\`\\`\\`</p>\n\n\n<p>```</p>\n\n<p>The same `search` adapter is called with different labels.</p>\n\n<h3>Key constraints</h3>\n\n<p>| Constraint                                         | Why                                                         |\n| -------------------------------------------------- | ----------------------------------------------------------- |\n| Adapter names must be valid JS identifiers         | They become `AsyncFunction` parameter names                 |\n| Don't use `<i>mdt</i>` prefix                           | Reserved for library-injected context keys                  |\n| Adapters are evaluated fresh on each `evalFn` call | No caching — each expansion re-evaluates                    |\n| Return `{ insert }` to produce output              | Any other return keeps the extruction transparent           |\n| Context is spread, not just the adapter            | All context keys are available — plan namespace accordingly |</p>\n\n<h3>6. E2E tests</h3>\n\n<p>Test the full player-paper.js integration: `.mdt` file fetch → compile →\nrun with evalBody + adapters → rebuild clean md → render.</p>\n\n<hr/>\n\n<h2>Conversion tree — transclusion provenance</h2>\n\n<p>When an mdt document transcludes content from source fragments (via extructions), the produced fragments have virtual trail positions in the generated document. To resolve these back to the real source fragments, the mdt runner produces a <b>conversion tree</b> alongside the generated text.</p>\n\n<h3>sourceFragment field</h3>\n\n<p>Each `Fragment` now carries an optional `sourceFragment`:</p>\n\n<p>```js</p>\n\n<p>{\ntrail: \"a/x\",\nheading: \"## <!-- ... -->\",\nbody: \"hello\",\nsourceFragment: null | { fn: \"paper/real.mdd\", refId: \"paper/real.mdd::real/heading\" }\n}</p>\n\n\n<p>```</p>\n\n<ul><li>`buildFragment()` — regular headings: `sourceFragment: null`</li><li>`buildInjectFragment()` — injected raw content: `sourceFragment: null`</li><li>`buildInsertFragment()` — extruction-produced fragments: reads `cmd.source`</li></ul>\n\n<h3>Extruction protocol — insert() extended</h3>\n\n<p>`insert()` accepts an optional second argument `{ source }`:</p>\n\n<p>```js</p>\n\n<p>// without source (existing behavior)\nreturn [insert(children)];</p>\n\n<p>// with source tag (new)\nreturn [\ninsert(children, {\nsource: { fn: \"paper/real.mdd\", refId: \"paper/real.mdd::real/heading\" },\n}),\n];</p>\n\n\n<p>```</p>\n\n<p>The `source` object flows from the command → `buildInsertFragment` → Fragment → consumer's conversion tree.</p>\n\n<h3>Consumer collects conversion tree</h3>\n\n<p>The consumer iterates fragments and builds a `Map<trail, sourceFragment>`:</p>\n\n<p>```js</p>\n\n<p>const conversionTree = new Map();\nfor await (const frag of doc) {\nif (frag.sourceFragment) {\nconversionTree.set(frag.trail, frag.sourceFragment);\n}\n}</p>\n\n\n<p>```</p>\n\n<h3>Rendering uses conversion tree</h3>\n\n<p>`renderFractalCollageOfPaper` accepts `{ conversionTree }` in opts. In `renderAfterEffect`, the rendering trail (semicolons) is converted to `/`-separated key and looked up in the tree. If found, the source `refId` is used as `data-fragmentRef` instead of the computed virtual refId.</p>\n\n<h3>Trail format conversion</h3>\n\n<p>| Context                | Format        | Example                                   |\n| ---------------------- | ------------- | ----------------------------------------- |\n| mdt Fragment.trail     | `/`-separated | `\"a/x\"`                                   |\n| rendering data-trail   | `;`-separated | `\"a;x\"`                                   |\n| conversionTree key     | `/`-separated | `\"a/x\"`                                   |\n| buildFragmentRef input | `;`-separated | `buildFragmentRef(base, \"a;x\", sanitize)` |</p>\n\n<h3>Changed files</h3>\n\n<ul><li>`mdt/mdt.js` — insert() helper, buildInsertFragment/buildInjectFragment/buildFragment, processExtructionResult</li><li>`player/player-paper.js` — .mdt case collects conversionTree during fragment walk, passes to renderFractalCollageOfPaper</li><li>`player/player-utils.js` — renderFractalCollageOfPaper accepts conversionTree, resolves source refId</li><li>`mdt/mdt.test.mjs` — 5 new tests: sourceFragment presence/absence, conversionTree collection</li></ul>"},{"id":"/root/children/328","type":"heading","loc":{"start":57309,"end":57343,"line":{"s":1695,"e":1695,"code":["### Rendering uses conversion tree"]},"column":{"s":0,"e":34}},"dim":["","heading.328"],"code":"### Rendering uses conversion tree","symbName":"heading","symbRange":[57345,57628],"symbRangeL":[1695,1698],"outerCode":"\n`renderFractalCollageOfPaper` accepts `{ conversionTree }` in opts. In `renderAfterEffect`, the rendering trail (semicolons) is converted to `/`-separated key and looked up in the tree. If found, the source `refId` is used as `data-fragmentRef` instead of the computed virtual refId.","outerHtml":"\n<p>`renderFractalCollageOfPaper` accepts `{ conversionTree }` in opts. In `renderAfterEffect`, the rendering trail (semicolons) is converted to `/`-separated key and looked up in the tree. If found, the source `refId` is used as `data-fragmentRef` instead of the computed virtual refId.</p>"},{"id":"/root/children/328/children/0","type":"text","loc":{"start":57313,"end":57343,"line":{"s":1695,"e":1695,"code":["### Rendering uses conversion tree"]},"column":{"s":4,"e":34}},"dim":["","heading.328","text.0"],"code":"Rendering uses conversion tree"},{"id":"/root/children/329","type":"paragraph","loc":{"start":57345,"end":57628,"line":{"s":1697,"e":1697,"code":["`renderFractalCollageOfPaper` accepts `{ conversionTree }` in opts. In `renderAfterEffect`, the rendering trail (semicolons) is converted to `/`-separated key and looked up in the tree. If found, the source `refId` is used as `data-fragmentRef` instead of the computed virtual refId."]},"column":{"s":0,"e":283}},"dim":["","paragraph.329"],"code":"`renderFractalCollageOfPaper` accepts `{ conversionTree }` in opts. In `renderAfterEffect`, the rendering trail (semicolons) is converted to `/`-separated key and looked up in the tree. If found, the source `refId` is used as `data-fragmentRef` instead of the computed virtual refId."},{"id":"/root/children/329/children/0","type":"inlineCode","loc":{"start":57345,"end":57374,"line":{"s":1697,"e":1697,"code":["`renderFractalCollageOfPaper` accepts `{ conversionTree }` in opts. In `renderAfterEffect`, the rendering trail (semicolons) is converted to `/`-separated key and looked up in the tree. If found, the source `refId` is used as `data-fragmentRef` instead of the computed virtual refId."]},"column":{"s":0,"e":29}},"dim":["","paragraph.329","inlineCode.0"],"code":"`renderFractalCollageOfPaper`"},{"id":"/root/children/329/children/1","type":"text","loc":{"start":57374,"end":57383,"line":{"s":1697,"e":1697,"code":["`renderFractalCollageOfPaper` accepts `{ conversionTree }` in opts. In `renderAfterEffect`, the rendering trail (semicolons) is converted to `/`-separated key and looked up in the tree. If found, the source `refId` is used as `data-fragmentRef` instead of the computed virtual refId."]},"column":{"s":29,"e":38}},"dim":["","paragraph.329","text.1"],"code":" accepts "},{"id":"/root/children/329/children/2","type":"inlineCode","loc":{"start":57383,"end":57403,"line":{"s":1697,"e":1697,"code":["`renderFractalCollageOfPaper` accepts `{ conversionTree }` in opts. In `renderAfterEffect`, the rendering trail (semicolons) is converted to `/`-separated key and looked up in the tree. If found, the source `refId` is used as `data-fragmentRef` instead of the computed virtual refId."]},"column":{"s":38,"e":58}},"dim":["","paragraph.329","inlineCode.2"],"code":"`{ conversionTree }`"},{"id":"/root/children/329/children/3","type":"text","loc":{"start":57403,"end":57416,"line":{"s":1697,"e":1697,"code":["`renderFractalCollageOfPaper` accepts `{ conversionTree }` in opts. In `renderAfterEffect`, the rendering trail (semicolons) is converted to `/`-separated key and looked up in the tree. If found, the source `refId` is used as `data-fragmentRef` instead of the computed virtual refId."]},"column":{"s":58,"e":71}},"dim":["","paragraph.329","text.3"],"code":" in opts. In "},{"id":"/root/children/329/children/4","type":"inlineCode","loc":{"start":57416,"end":57435,"line":{"s":1697,"e":1697,"code":["`renderFractalCollageOfPaper` accepts `{ conversionTree }` in opts. In `renderAfterEffect`, the rendering trail (semicolons) is converted to `/`-separated key and looked up in the tree. If found, the source `refId` is used as `data-fragmentRef` instead of the computed virtual refId."]},"column":{"s":71,"e":90}},"dim":["","paragraph.329","inlineCode.4"],"code":"`renderAfterEffect`"},{"id":"/root/children/329/children/5","type":"text","loc":{"start":57435,"end":57486,"line":{"s":1697,"e":1697,"code":["`renderFractalCollageOfPaper` accepts `{ conversionTree }` in opts. In `renderAfterEffect`, the rendering trail (semicolons) is converted to `/`-separated key and looked up in the tree. If found, the source `refId` is used as `data-fragmentRef` instead of the computed virtual refId."]},"column":{"s":90,"e":141}},"dim":["","paragraph.329","text.5"],"code":", the rendering trail (semicolons) is converted to "},{"id":"/root/children/329/children/6","type":"inlineCode","loc":{"start":57486,"end":57489,"line":{"s":1697,"e":1697,"code":["`renderFractalCollageOfPaper` accepts `{ conversionTree }` in opts. In `renderAfterEffect`, the rendering trail (semicolons) is converted to `/`-separated key and looked up in the tree. If found, the source `refId` is used as `data-fragmentRef` instead of the computed virtual refId."]},"column":{"s":141,"e":144}},"dim":["","paragraph.329","inlineCode.6"],"code":"`/`"},{"id":"/root/children/329/children/7","type":"text","loc":{"start":57489,"end":57552,"line":{"s":1697,"e":1697,"code":["`renderFractalCollageOfPaper` accepts `{ conversionTree }` in opts. In `renderAfterEffect`, the rendering trail (semicolons) is converted to `/`-separated key and looked up in the tree. If found, the source `refId` is used as `data-fragmentRef` instead of the computed virtual refId."]},"column":{"s":144,"e":207}},"dim":["","paragraph.329","text.7"],"code":"-separated key and looked up in the tree. If found, the source "},{"id":"/root/children/329/children/8","type":"inlineCode","loc":{"start":57552,"end":57559,"line":{"s":1697,"e":1697,"code":["`renderFractalCollageOfPaper` accepts `{ conversionTree }` in opts. In `renderAfterEffect`, the rendering trail (semicolons) is converted to `/`-separated key and looked up in the tree. If found, the source `refId` is used as `data-fragmentRef` instead of the computed virtual refId."]},"column":{"s":207,"e":214}},"dim":["","paragraph.329","inlineCode.8"],"code":"`refId`"},{"id":"/root/children/329/children/9","type":"text","loc":{"start":57559,"end":57571,"line":{"s":1697,"e":1697,"code":["`renderFractalCollageOfPaper` accepts `{ conversionTree }` in opts. In `renderAfterEffect`, the rendering trail (semicolons) is converted to `/`-separated key and looked up in the tree. If found, the source `refId` is used as `data-fragmentRef` instead of the computed virtual refId."]},"column":{"s":214,"e":226}},"dim":["","paragraph.329","text.9"],"code":" is used as "},{"id":"/root/children/329/children/10","type":"inlineCode","loc":{"start":57571,"end":57589,"line":{"s":1697,"e":1697,"code":["`renderFractalCollageOfPaper` accepts `{ conversionTree }` in opts. In `renderAfterEffect`, the rendering trail (semicolons) is converted to `/`-separated key and looked up in the tree. If found, the source `refId` is used as `data-fragmentRef` instead of the computed virtual refId."]},"column":{"s":226,"e":244}},"dim":["","paragraph.329","inlineCode.10"],"code":"`data-fragmentRef`"},{"id":"/root/children/329/children/11","type":"text","loc":{"start":57589,"end":57628,"line":{"s":1697,"e":1697,"code":["`renderFractalCollageOfPaper` accepts `{ conversionTree }` in opts. In `renderAfterEffect`, the rendering trail (semicolons) is converted to `/`-separated key and looked up in the tree. If found, the source `refId` is used as `data-fragmentRef` instead of the computed virtual refId."]},"column":{"s":244,"e":283}},"dim":["","paragraph.329","text.11"],"code":" instead of the computed virtual refId."},{"id":"/root/children/330","type":"heading","loc":{"start":57630,"end":57657,"line":{"s":1699,"e":1699,"code":["### Trail format conversion"]},"column":{"s":0,"e":27}},"dim":["","heading.330"],"code":"### Trail format conversion","symbName":"heading","symbRange":[57659,58180],"symbRangeL":[1699,1707],"outerCode":"\n| Context                | Format        | Example                                   |\n| ---------------------- | ------------- | ----------------------------------------- |\n| mdt Fragment.trail     | `/`-separated | `\"a/x\"`                                   |\n| rendering data-trail   | `;`-separated | `\"a;x\"`                                   |\n| conversionTree key     | `/`-separated | `\"a/x\"`                                   |\n| buildFragmentRef input | `;`-separated | `buildFragmentRef(base, \"a;x\", sanitize)` |","outerHtml":"\n<p>| Context                | Format        | Example                                   |\n| ---------------------- | ------------- | ----------------------------------------- |\n| mdt Fragment.trail     | `/`-separated | `\"a/x\"`                                   |\n| rendering data-trail   | `;`-separated | `\"a;x\"`                                   |\n| conversionTree key     | `/`-separated | `\"a/x\"`                                   |\n| buildFragmentRef input | `;`-separated | `buildFragmentRef(base, \"a;x\", sanitize)` |</p>"},{"id":"/root/children/330/children/0","type":"text","loc":{"start":57634,"end":57657,"line":{"s":1699,"e":1699,"code":["### Trail format conversion"]},"column":{"s":4,"e":27}},"dim":["","heading.330","text.0"],"code":"Trail format conversion"},{"id":"/root/children/331","type":"paragraph","loc":{"start":57659,"end":58180,"line":{"s":1701,"e":1706,"code":["| Context                | Format        | Example                                   |","| ---------------------- | ------------- | ----------------------------------------- |","| mdt Fragment.trail     | `/`-separated | `\"a/x\"`                                   |","| rendering data-trail   | `;`-separated | `\"a;x\"`                                   |","| conversionTree key     | `/`-separated | `\"a/x\"`                                   |","| buildFragmentRef input | `;`-separated | `buildFragmentRef(base, \"a;x\", sanitize)` |"]},"column":{"s":0,"e":86}},"dim":["","paragraph.331"],"code":"| Context                | Format        | Example                                   |\n| ---------------------- | ------------- | ----------------------------------------- |\n| mdt Fragment.trail     | `/`-separated | `\"a/x\"`                                   |\n| rendering data-trail   | `;`-separated | `\"a;x\"`                                   |\n| conversionTree key     | `/`-separated | `\"a/x\"`                                   |\n| buildFragmentRef input | `;`-separated | `buildFragmentRef(base, \"a;x\", sanitize)` |"},{"id":"/root/children/331/children/0","type":"text","loc":{"start":57659,"end":57860,"line":{"s":1701,"e":1703,"code":["| Context                | Format        | Example                                   |","| ---------------------- | ------------- | ----------------------------------------- |","| mdt Fragment.trail     | `/`-separated | `\"a/x\"`                                   |"]},"column":{"s":0,"e":27}},"dim":["","paragraph.331","text.0"],"code":"| Context                | Format        | Example                                   |\n| ---------------------- | ------------- | ----------------------------------------- |\n| mdt Fragment.trail     | "},{"id":"/root/children/331/children/1","type":"inlineCode","loc":{"start":57860,"end":57863,"line":{"s":1703,"e":1703,"code":["| mdt Fragment.trail     | `/`-separated | `\"a/x\"`                                   |"]},"column":{"s":27,"e":30}},"dim":["","paragraph.331","inlineCode.1"],"code":"`/`"},{"id":"/root/children/331/children/2","type":"text","loc":{"start":57863,"end":57876,"line":{"s":1703,"e":1703,"code":["| mdt Fragment.trail     | `/`-separated | `\"a/x\"`                                   |"]},"column":{"s":30,"e":43}},"dim":["","paragraph.331","text.2"],"code":"-separated | "},{"id":"/root/children/331/children/3","type":"inlineCode","loc":{"start":57876,"end":57883,"line":{"s":1703,"e":1703,"code":["| mdt Fragment.trail     | `/`-separated | `\"a/x\"`                                   |"]},"column":{"s":43,"e":50}},"dim":["","paragraph.331","inlineCode.3"],"code":"`\"a/x\"`"},{"id":"/root/children/331/children/4","type":"text","loc":{"start":57883,"end":57947,"line":{"s":1703,"e":1704,"code":["| mdt Fragment.trail     | `/`-separated | `\"a/x\"`                                   |","| rendering data-trail   | `;`-separated | `\"a;x\"`                                   |"]},"column":{"s":50,"e":27}},"dim":["","paragraph.331","text.4"],"code":"                                   |\n| rendering data-trail   | "},{"id":"/root/children/331/children/5","type":"inlineCode","loc":{"start":57947,"end":57950,"line":{"s":1704,"e":1704,"code":["| rendering data-trail   | `;`-separated | `\"a;x\"`                                   |"]},"column":{"s":27,"e":30}},"dim":["","paragraph.331","inlineCode.5"],"code":"`;`"},{"id":"/root/children/331/children/6","type":"text","loc":{"start":57950,"end":57963,"line":{"s":1704,"e":1704,"code":["| rendering data-trail   | `;`-separated | `\"a;x\"`                                   |"]},"column":{"s":30,"e":43}},"dim":["","paragraph.331","text.6"],"code":"-separated | "},{"id":"/root/children/331/children/7","type":"inlineCode","loc":{"start":57963,"end":57970,"line":{"s":1704,"e":1704,"code":["| rendering data-trail   | `;`-separated | `\"a;x\"`                                   |"]},"column":{"s":43,"e":50}},"dim":["","paragraph.331","inlineCode.7"],"code":"`\"a;x\"`"},{"id":"/root/children/331/children/8","type":"text","loc":{"start":57970,"end":58034,"line":{"s":1704,"e":1705,"code":["| rendering data-trail   | `;`-separated | `\"a;x\"`                                   |","| conversionTree key     | `/`-separated | `\"a/x\"`                                   |"]},"column":{"s":50,"e":27}},"dim":["","paragraph.331","text.8"],"code":"                                   |\n| conversionTree key     | "},{"id":"/root/children/331/children/9","type":"inlineCode","loc":{"start":58034,"end":58037,"line":{"s":1705,"e":1705,"code":["| conversionTree key     | `/`-separated | `\"a/x\"`                                   |"]},"column":{"s":27,"e":30}},"dim":["","paragraph.331","inlineCode.9"],"code":"`/`"},{"id":"/root/children/331/children/10","type":"text","loc":{"start":58037,"end":58050,"line":{"s":1705,"e":1705,"code":["| conversionTree key     | `/`-separated | `\"a/x\"`                                   |"]},"column":{"s":30,"e":43}},"dim":["","paragraph.331","text.10"],"code":"-separated | "},{"id":"/root/children/331/children/11","type":"inlineCode","loc":{"start":58050,"end":58057,"line":{"s":1705,"e":1705,"code":["| conversionTree key     | `/`-separated | `\"a/x\"`                                   |"]},"column":{"s":43,"e":50}},"dim":["","paragraph.331","inlineCode.11"],"code":"`\"a/x\"`"},{"id":"/root/children/331/children/12","type":"text","loc":{"start":58057,"end":58121,"line":{"s":1705,"e":1706,"code":["| conversionTree key     | `/`-separated | `\"a/x\"`                                   |","| buildFragmentRef input | `;`-separated | `buildFragmentRef(base, \"a;x\", sanitize)` |"]},"column":{"s":50,"e":27}},"dim":["","paragraph.331","text.12"],"code":"                                   |\n| buildFragmentRef input | "},{"id":"/root/children/331/children/13","type":"inlineCode","loc":{"start":58121,"end":58124,"line":{"s":1706,"e":1706,"code":["| buildFragmentRef input | `;`-separated | `buildFragmentRef(base, \"a;x\", sanitize)` |"]},"column":{"s":27,"e":30}},"dim":["","paragraph.331","inlineCode.13"],"code":"`;`"},{"id":"/root/children/331/children/14","type":"text","loc":{"start":58124,"end":58137,"line":{"s":1706,"e":1706,"code":["| buildFragmentRef input | `;`-separated | `buildFragmentRef(base, \"a;x\", sanitize)` |"]},"column":{"s":30,"e":43}},"dim":["","paragraph.331","text.14"],"code":"-separated | "},{"id":"/root/children/331/children/15","type":"inlineCode","loc":{"start":58137,"end":58178,"line":{"s":1706,"e":1706,"code":["| buildFragmentRef input | `;`-separated | `buildFragmentRef(base, \"a;x\", sanitize)` |"]},"column":{"s":43,"e":84}},"dim":["","paragraph.331","inlineCode.15"],"code":"`buildFragmentRef(base, \"a;x\", sanitize)`"},{"id":"/root/children/331/children/16","type":"text","loc":{"start":58178,"end":58180,"line":{"s":1706,"e":1706,"code":["| buildFragmentRef input | `;`-separated | `buildFragmentRef(base, \"a;x\", sanitize)` |"]},"column":{"s":84,"e":86}},"dim":["","paragraph.331","text.16"],"code":" |"},{"id":"/root/children/332","type":"heading","loc":{"start":58182,"end":58199,"line":{"s":1708,"e":1708,"code":["### Changed files"]},"column":{"s":0,"e":17}},"dim":["","heading.332"],"code":"### Changed files","symbName":"heading","symbRange":[58201,58634],"symbRangeL":[1708,1714],"outerCode":"\n- `mdt/mdt.js` — insert() helper, buildInsertFragment/buildInjectFragment/buildFragment, processExtructionResult\n- `player/player-paper.js` — .mdt case collects conversionTree during fragment walk, passes to renderFractalCollageOfPaper\n- `player/player-utils.js` — renderFractalCollageOfPaper accepts conversionTree, resolves source refId\n- `mdt/mdt.test.mjs` — 5 new tests: sourceFragment presence/absence, conversionTree collection","outerHtml":"\n<ul><li>`mdt/mdt.js` — insert() helper, buildInsertFragment/buildInjectFragment/buildFragment, processExtructionResult</li><li>`player/player-paper.js` — .mdt case collects conversionTree during fragment walk, passes to renderFractalCollageOfPaper</li><li>`player/player-utils.js` — renderFractalCollageOfPaper accepts conversionTree, resolves source refId</li><li>`mdt/mdt.test.mjs` — 5 new tests: sourceFragment presence/absence, conversionTree collection</li></ul>"},{"id":"/root/children/332/children/0","type":"text","loc":{"start":58186,"end":58199,"line":{"s":1708,"e":1708,"code":["### Changed files"]},"column":{"s":4,"e":17}},"dim":["","heading.332","text.0"],"code":"Changed files"},{"id":"/root/children/333","type":"list","loc":{"start":58201,"end":58634,"line":{"s":1710,"e":1713,"code":["- `mdt/mdt.js` — insert() helper, buildInsertFragment/buildInjectFragment/buildFragment, processExtructionResult","- `player/player-paper.js` — .mdt case collects conversionTree during fragment walk, passes to renderFractalCollageOfPaper","- `player/player-utils.js` — renderFractalCollageOfPaper accepts conversionTree, resolves source refId","- `mdt/mdt.test.mjs` — 5 new tests: sourceFragment presence/absence, conversionTree collection"]},"column":{"s":0,"e":94}},"dim":["","list.333"],"code":"- `mdt/mdt.js` — insert() helper, buildInsertFragment/buildInjectFragment/buildFragment, processExtructionResult\n- `player/player-paper.js` — .mdt case collects conversionTree during fragment walk, passes to renderFractalCollageOfPaper\n- `player/player-utils.js` — renderFractalCollageOfPaper accepts conversionTree, resolves source refId\n- `mdt/mdt.test.mjs` — 5 new tests: sourceFragment presence/absence, conversionTree collection","symbName":"list","symbRange":[58636,null],"symbRangeL":[1710,1714],"outerCode":"- `player/player-paper.js` — .mdt case collects conversionTree during fragment walk, passes to renderFractalCollageOfPaper\n- `player/player-utils.js` — renderFractalCollageOfPaper accepts conversionTree, resolves source refId\n- `mdt/mdt.test.mjs` — 5 new tests: sourceFragment presence/absence, conversionTree collection","outerHtml":"<ul><li>`player/player-paper.js` — .mdt case collects conversionTree during fragment walk, passes to renderFractalCollageOfPaper</li><li>`player/player-utils.js` — renderFractalCollageOfPaper accepts conversionTree, resolves source refId</li><li>`mdt/mdt.test.mjs` — 5 new tests: sourceFragment presence/absence, conversionTree collection</li></ul>"},{"id":"/root/children/333/children/0","type":"listItem","loc":{"start":58201,"end":58313,"line":{"s":1710,"e":1710,"code":["- `mdt/mdt.js` — insert() helper, buildInsertFragment/buildInjectFragment/buildFragment, processExtructionResult"]},"column":{"s":0,"e":112}},"dim":["","list.333","listItem.0"],"code":"- `mdt/mdt.js` — insert() helper, buildInsertFragment/buildInjectFragment/buildFragment, processExtructionResult"},{"id":"/root/children/333/children/0/children/0","type":"paragraph","loc":{"start":58203,"end":58313,"line":{"s":1710,"e":1710,"code":["- `mdt/mdt.js` — insert() helper, buildInsertFragment/buildInjectFragment/buildFragment, processExtructionResult"]},"column":{"s":2,"e":112}},"dim":["","list.333","listItem.0","paragraph.0"],"code":"`mdt/mdt.js` — insert() helper, buildInsertFragment/buildInjectFragment/buildFragment, processExtructionResult"},{"id":"/root/children/333/children/0/children/0/children/0","type":"inlineCode","loc":{"start":58203,"end":58215,"line":{"s":1710,"e":1710,"code":["- `mdt/mdt.js` — insert() helper, buildInsertFragment/buildInjectFragment/buildFragment, processExtructionResult"]},"column":{"s":2,"e":14}},"dim":["","list.333","listItem.0","paragraph.0","inlineCode.0"],"code":"`mdt/mdt.js`"},{"id":"/root/children/333/children/0/children/0/children/1","type":"text","loc":{"start":58215,"end":58313,"line":{"s":1710,"e":1710,"code":["- `mdt/mdt.js` — insert() helper, buildInsertFragment/buildInjectFragment/buildFragment, processExtructionResult"]},"column":{"s":14,"e":112}},"dim":["","list.333","listItem.0","paragraph.0","text.1"],"code":" — insert() helper, buildInsertFragment/buildInjectFragment/buildFragment, processExtructionResult"},{"id":"/root/children/333/children/1","type":"listItem","loc":{"start":58314,"end":58436,"line":{"s":1711,"e":1711,"code":["- `player/player-paper.js` — .mdt case collects conversionTree during fragment walk, passes to renderFractalCollageOfPaper"]},"column":{"s":0,"e":122}},"dim":["","list.333","listItem.1"],"code":"- `player/player-paper.js` — .mdt case collects conversionTree during fragment walk, passes to renderFractalCollageOfPaper"},{"id":"/root/children/333/children/1/children/0","type":"paragraph","loc":{"start":58316,"end":58436,"line":{"s":1711,"e":1711,"code":["- `player/player-paper.js` — .mdt case collects conversionTree during fragment walk, passes to renderFractalCollageOfPaper"]},"column":{"s":2,"e":122}},"dim":["","list.333","listItem.1","paragraph.0"],"code":"`player/player-paper.js` — .mdt case collects conversionTree during fragment walk, passes to renderFractalCollageOfPaper"},{"id":"/root/children/333/children/1/children/0/children/0","type":"inlineCode","loc":{"start":58316,"end":58340,"line":{"s":1711,"e":1711,"code":["- `player/player-paper.js` — .mdt case collects conversionTree during fragment walk, passes to renderFractalCollageOfPaper"]},"column":{"s":2,"e":26}},"dim":["","list.333","listItem.1","paragraph.0","inlineCode.0"],"code":"`player/player-paper.js`"},{"id":"/root/children/333/children/1/children/0/children/1","type":"text","loc":{"start":58340,"end":58436,"line":{"s":1711,"e":1711,"code":["- `player/player-paper.js` — .mdt case collects conversionTree during fragment walk, passes to renderFractalCollageOfPaper"]},"column":{"s":26,"e":122}},"dim":["","list.333","listItem.1","paragraph.0","text.1"],"code":" — .mdt case collects conversionTree during fragment walk, passes to renderFractalCollageOfPaper"},{"id":"/root/children/333/children/2","type":"listItem","loc":{"start":58437,"end":58539,"line":{"s":1712,"e":1712,"code":["- `player/player-utils.js` — renderFractalCollageOfPaper accepts conversionTree, resolves source refId"]},"column":{"s":0,"e":102}},"dim":["","list.333","listItem.2"],"code":"- `player/player-utils.js` — renderFractalCollageOfPaper accepts conversionTree, resolves source refId"},{"id":"/root/children/333/children/2/children/0","type":"paragraph","loc":{"start":58439,"end":58539,"line":{"s":1712,"e":1712,"code":["- `player/player-utils.js` — renderFractalCollageOfPaper accepts conversionTree, resolves source refId"]},"column":{"s":2,"e":102}},"dim":["","list.333","listItem.2","paragraph.0"],"code":"`player/player-utils.js` — renderFractalCollageOfPaper accepts conversionTree, resolves source refId"},{"id":"/root/children/333/children/2/children/0/children/0","type":"inlineCode","loc":{"start":58439,"end":58463,"line":{"s":1712,"e":1712,"code":["- `player/player-utils.js` — renderFractalCollageOfPaper accepts conversionTree, resolves source refId"]},"column":{"s":2,"e":26}},"dim":["","list.333","listItem.2","paragraph.0","inlineCode.0"],"code":"`player/player-utils.js`"},{"id":"/root/children/333/children/2/children/0/children/1","type":"text","loc":{"start":58463,"end":58539,"line":{"s":1712,"e":1712,"code":["- `player/player-utils.js` — renderFractalCollageOfPaper accepts conversionTree, resolves source refId"]},"column":{"s":26,"e":102}},"dim":["","list.333","listItem.2","paragraph.0","text.1"],"code":" — renderFractalCollageOfPaper accepts conversionTree, resolves source refId"},{"id":"/root/children/333/children/3","type":"listItem","loc":{"start":58540,"end":58634,"line":{"s":1713,"e":1713,"code":["- `mdt/mdt.test.mjs` — 5 new tests: sourceFragment presence/absence, conversionTree collection"]},"column":{"s":0,"e":94}},"dim":["","list.333","listItem.3"],"code":"- `mdt/mdt.test.mjs` — 5 new tests: sourceFragment presence/absence, conversionTree collection"},{"id":"/root/children/333/children/3/children/0","type":"paragraph","loc":{"start":58542,"end":58634,"line":{"s":1713,"e":1713,"code":["- `mdt/mdt.test.mjs` — 5 new tests: sourceFragment presence/absence, conversionTree collection"]},"column":{"s":2,"e":94}},"dim":["","list.333","listItem.3","paragraph.0"],"code":"`mdt/mdt.test.mjs` — 5 new tests: sourceFragment presence/absence, conversionTree collection"},{"id":"/root/children/333/children/3/children/0/children/0","type":"inlineCode","loc":{"start":58542,"end":58560,"line":{"s":1713,"e":1713,"code":["- `mdt/mdt.test.mjs` — 5 new tests: sourceFragment presence/absence, conversionTree collection"]},"column":{"s":2,"e":20}},"dim":["","list.333","listItem.3","paragraph.0","inlineCode.0"],"code":"`mdt/mdt.test.mjs`"},{"id":"/root/children/333/children/3/children/0/children/1","type":"text","loc":{"start":58560,"end":58634,"line":{"s":1713,"e":1713,"code":["- `mdt/mdt.test.mjs` — 5 new tests: sourceFragment presence/absence, conversionTree collection"]},"column":{"s":20,"e":94}},"dim":["","list.333","listItem.3","paragraph.0","text.1"],"code":" — 5 new tests: sourceFragment presence/absence, conversionTree collection"},{"id":"/root/children/334","type":"code","loc":{"start":58636,"end":58640,"line":{"s":1715,"e":1716,"code":["```",""]},"column":{"s":0,"e":0}},"dim":["","code.334"],"code":"```\n","symbName":"code","symbRange":[null,null],"symbRangeL":[null,null],"outerCode":"","outerHtml":""}]